代码之家  ›  专栏  ›  技术社区  ›  SmacL

我可以使用C++中的[]操作符来创建虚拟数组吗?

  •  6
  • SmacL  · 技术社区  · 14 年前

    我有一个很大的代码库,最初C移植到C++很多年前,它是在大量的空间数据数组上运行的。这些数组包含表示点的结构和表示曲面模型的三角形实体。我需要重构代码,使这些实体在内部存储的特定方式因特定场景而异。例如,如果点位于一个规则的平面网格上,我不需要存储X和Y坐标,因为它们可以动态计算,三角形也可以。同样,我希望利用核心外的工具,例如 STXXL 用于存储。最简单的方法是用put和get类型函数替换数组访问,例如

    point[i].x = XV;
    

    变成

    Point p = GetPoint(i);
    p.x = XV;
    PutPoint(i,p);
    

    正如您可以想象的那样,这是一个非常繁琐的基于大型代码的重构,很容易在过程中出现各种错误。我要做的是编写一个类,通过重载[]运算符来模拟数组。由于数组已经存在于堆中,并且使用realloc四处移动,因此代码已经假定对数组的引用,例如

    point *p = point + i;
    

    不能使用。这个类可以写吗?例如,根据[]运算符编写以下方法;

    void MyClass::PutPoint(int Index, Point p)
    {
       if (m_StorageStrategy == RegularGrid)
       {
          int xoffs,yoffs;
          ComputeGridFromIndex(Index,xoffs,yoffs);
          StoreGridPoint(xoffs,yoffs,p.z);
        } else
           m_PointArray[Index] = p;   
      }
    }
    
    Point MyClass::GetPoint(int Index)
    {
       if (m_StorageStrategy == RegularGrid)
       {
          int xoffs,yoffs;
          ComputeGridFromIndex(Index,xoffs,yoffs);
          return GetGridPoint(xoffs,yoffs);   // GetGridPoint returns Point
        } else
           return m_PointArray[Index];   
      }
    }
    

    我担心的是,我看到的所有数组类都倾向于通过引用传递,而我认为我必须通过值传递结构。我认为它应该工作而不是表现,任何人都能看到这种方法的任何主要缺陷。注意:我必须传递值的原因是

    point[a].z = point[b].z + point[c].z
    

    以便在基础存储类型不同的情况下正常工作。

    4 回复  |  直到 14 年前
        1
  •  5
  •   Pete Kirkham    14 年前

    不需要按值传递数组。要改变数组中的值,需要两个版本的 operator[] 一个返回一个引用(变位)和一个常量引用。

    原则上没有理由不使用 操作员[ ] ,只要您不需要在运行时改变存储的类型-没有虚拟运算符,所以如果您想要运行时多态性,那么您需要一个命名函数。在这种情况下,您可以创建一个简单的 struct 它将运算符调用适应于函数调用(尽管它更依赖于存储API——如果代码假定分配给点的成员变量会更改存储的数据,则可能还必须将点类型设置为模板变量,以便可以覆盖此变量)。

    看看您的示例代码,它对存储策略进行了测试。不要这样做。要么使用OO并让存储对象实现一个公共的虚拟接口,要么(可能更好)使用模板编程来改变存储机制。

    如果你看一下 std::vector (在最近的C++标准中),有可能具有动态存储和允许使用指针算术的东西,尽管这需要连续存储。考虑到您的一些值是即时创建的,因此可能不值得对您的实现进行这种限制,但是约束本身并不阻止使用 操作员[ ] .

        2
  •  2
  •   Suma    14 年前

    你想要的是可能的,但是由于你也需要写访问,结果有时会更复杂一些。您需要的是setter函数返回的不是直接的“点写入访问”,而是一个临时副本,一旦副本超出范围,它将进行写入。

    以下代码片段试图概述解决方案:

    class PointVector
    {
      MyClass container_;
    
      public:
      class PointExSet: public Point
      {
        MyClass &container_;
        int index_;
    
        public:
        PointExSet(MyClass &container, int index)
          :Point(container.GetVector(index)),container_(container),index_(index)
        {
        }
    
        ~PointExSet()
        {
          container_.PutVector(index_) = *this;
        }
      };
    
      PointExSet operator [] (int i)
      {
        return PointExSet(container_,i);
      }
    };
    

    它不像你希望的那样好,但是恐怕你不能在C++中得到更好的解决方案。

        3
  •  1
  •   Alsk    14 年前

    要完全控制数组上的操作,operator[]应该返回一个特殊的对象(很久以前发明的,称为“cursor”),它将为您处理操作。 举个例子:

    class Container
    {
      PointCursor operator [] (int i)
      {
        return PointCursor(this,i);
      }
    };
    class PointCursor
    {
    public:
        PointCursor(_container, _i)
           : container(_container), i(_i),
             //initialize subcursor
             x(container, i) {}     
    
        //subcursor
        XCursor x;
    private:
       Container* container;
       int i;
    };
    class XCursor
    {
    public:
        XCursor(_container, _i)
          : container(_container), i(_i) {}
    
         XCursor& operator = (const XCursor& xc)
         {
              container[i].x = xc.container[xc.i].x;
              //or do whatever you want over x
         }
    
         Container* container;
         int i; 
    }
    //usage
    my_container[i].x = their_container[j].x; //calls XCursor::operator = ()
    
        4
  •  0
  •   Community PPrice    7 年前

    在阅读了以上答案之后,我决定皮特的答案有两种版本 operator[] 是前进的最好方式。为了在运行时处理类型之间的变形,我创建了一个新的数组模板类,它采用以下四个参数:

    template<class TYPE, class ARG_TYPE,class BASE_TYPE, class BASE_ARG_TYPE>
    class CMorphArray 
    {
    int GetSize() { return m_BaseData.GetSize(); }
    BOOL IsEmpty() { return m_BaseData.IsEmpty(); }
    
    // Accessing elements
    const TYPE& GetAt(int nIndex) const;
    TYPE& GetAt(int nIndex);
    void SetAt(int nIndex, ARG_TYPE newElement);
    const TYPE& ElementAt(int nIndex) const;
    TYPE& ElementAt(int nIndex);
    
    // Potentially growing the array
    int Add(ARG_TYPE newElement);
    
    // overloaded operator helpers
    const TYPE& operator[](int nIndex) const;
    TYPE& operator[](int nIndex);
    
       CBigArray<BASE_TYPE, BASE_ARG_TYPE>  m_BaseData;
    private:
       CBigArray<TYPE, ARG_TYPE>    m_RefCache;
       CBigArray<int, int&> m_RefIndex;
       CBigArray<int, int&> m_CacheIndex;
    
       virtual void Convert(BASE_TYPE,ARG_TYPE) = 0;
       virtual void Convert(TYPE,BASE_ARG_TYPE) = 0;
    
       void InitCache();
       TYPE&    GetCachedElement(int nIndex);
    };
    

    主数据存储在 m_BaseData 这是原始格式的数据,可以根据讨论的类型变化。 m_RefCache 是以预期格式缓存元素的辅助数组,并且 GetCachedElement 函数使用虚拟 Convert 函数在数据移入和移出缓存时转换数据。缓存需要至少与一次可以激活的同时引用的数量一样大,但在我的情况下,如果缓存变大,可能会受益于它减少所需的转换数量。虽然alsk的光标实现可能会很好地工作,但是给出的解决方案需要更少的对象副本和临时变量,并且应该提供更好的性能,这在本例中很重要。

    为老的MFC外观和感觉向所有的STL粉丝道歉;项目的其余部分是MFC,因此在本例中它更有意义。cbigaray是 related stack overflow question 这成为我处理大型阵列的基础。我希望今天完成实现,明天测试。如果这一切都砸在我身上,我会相应地编辑这篇文章。