我可以在C++中使用[]运算符来创建虚拟数组

Sma*_*acL 6 c++ dynamic-arrays

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

point[i].x = XV;
Run Code Online (Sandbox Code Playgroud)

Point p = GetPoint(i);
p.x = XV;
PutPoint(i,p);
Run Code Online (Sandbox Code Playgroud)

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

point *p = point + i;
Run Code Online (Sandbox Code Playgroud)

可能不会被使用.写这课是否可行?例如,根据[]运算符编写下面的方法;

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];   
  }
}
Run Code Online (Sandbox Code Playgroud)

我担心的是我看到的所有数组类都倾向于通过引用传递,而我认为我必须按值传递结构.我认为它应该比性能更有效,任何人都可以看到这种方法的任何重大缺陷.我必须通过值传递的原因是得到

point[a].z = point[b].z + point[c].z
Run Code Online (Sandbox Code Playgroud)

在底层存储类型变化的地方正常工作.

Pet*_*ham 5

您不应该按值传递数组.要改变数组中的值,您需要两个版本operator[],一个返回引用(mutate),另一个返回const引用.

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

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

如果你看一下std::vector(在最近的C++标准中)所做的保证,那么就可以拥有一些具有动态存储并允许使用指针算法的东西,尽管这需要连续的存储.鉴于您的某些值是动态创建的,可能不值得对您的实现施加限制,但约束本身并不妨碍使用operator[].


Sma*_*acL 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);
};
Run Code Online (Sandbox Code Playgroud)

主数据存储中存储m_BaseData有原始格式的数据,其类型可能会有所不同,如所讨论的。 m_RefCache是按预期格式缓存元素的辅助数组,该GetCachedElement函数使用虚拟Convert函数在数据移入和移出缓存时转换数据。缓存需要至少与在任何时刻都处于活动状态的同时引用的数量一样大,但在我的情况下,缓存可能会受益于更大的缓存,因为它减少了所需的转换数量。虽然 Alsk 的游标实现可能运行良好,但给出的解决方案需要更少的对象副本和临时变量,并且应该提供稍微更好的性能,这在这种情况下很重要。

对于旧版 MFC 的外观和感觉,向所有 STL 粉丝致歉;该项目的其余部分是 MFC,因此在这种情况下更有意义。CBigArray 是相关堆栈溢出问题的结果,该问题成为我处理大型数组的基础。我希望今天完成实施并明天进行测试。如果这一切都对我不利,我将相应地编辑这篇文章。