6

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

point[i].x = XV;

变成

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

可以想象,这是对大型代码库的非常繁琐的重构,在途中容易出现各种错误。我想做的是编写一个通过重载 [] 运算符来模仿数组的类。由于数组已经存在于堆上,并且随着 reallocs 移动,代码已经假设对数组的引用,例如

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];   
  }
}

我担心的是我见过的所有数组类都倾向于通过引用传递,而我认为我必须通过值传递结构。我认为它应该在性能之外起作用,任何人都可以看到这种方法的任何主要缺陷。nb 我必须按值传递的原因是为了得到

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

在底层存储类型不同的情况下正常工作。

4

4 回答 4

5

您不需要按值传递数组。为了改变数组中的值,您需要两个版本的operator[],一个返回一个引用(用于变异),一个返回一个 const 引用。

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

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

如果您查看std::vector(在最近的 C++ 标准中)做出的保证,那么可能会有一些具有动态存储并允许使用指针算术的东西,尽管这需要连续存储。鉴于您的某些值是动态创建的,可能不值得对您的实现进行限制,但约束本身并不会阻止使用operator[].

于 2010-05-11T09:17:54.570 回答
2

你想要什么是可能的,但由于你也需要写访问权限,结果有时会有点复杂。您想要的是 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++ 中获得更好的解决方案。

于 2010-05-11T09:30:08.117 回答
1

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

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 = ()
于 2010-05-11T12:13:11.340 回答
0

阅读上述答案后,我认为皮特的两个版本的答案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,因此在这种情况下更有意义。CBigArray 是相关堆栈溢出问题的结果,该问题成为我处理大型数组的基础。我希望今天完成实现,明天测试。如果这一切都对我不利,我将相应地编辑这篇文章。

于 2010-05-11T16:01:30.123 回答