6

私は、何年も前にもともと C を C++ に移植した大規模なコード ベースを持っており、空間データの多数の大規模な配列を操作しています。これらの配列には、サーフェス モデルを表す点および三角形のエンティティを表す構造体が含まれています。これらのエンティティが内部に格納される特定の方法が特定のシナリオで異なるように、コードをリファクタリングする必要があります。たとえば、ポイントが通常の平らなグリッド上にある場合、X 座標と Y 座標を保存する必要はありません。三角形と同様に、その場で計算できるからです。同様に、ストレージ用のSTXXLなどのコア ツール以外を活用したいと考えています。これを行う最も簡単な方法は、配列アクセスを put および get 型の関数に置き換えることです。

point[i].x = XV;

になる

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

ご想像のとおり、これは大規模なコード ベースでの非常に面倒なリファクタリングであり、途中であらゆる種類のエラーが発生しがちです。私がやりたいのは、[] 演算子をオーバーロードして配列を模倣するクラスを作成することです。配列はすでにヒープ上に存在し、再割り当てで移動するため、コードは次のような配列への参照を既に想定しています。

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

配列を値で渡す必要はありません。配列内の値を変更するには、2つのバージョンが必要です。1つはoperator[](変更するための)参照を返し、もう1つはconst参照を返します。

実行時にストレージのタイプを変更する必要がない限り、原則としてを使用しない理由はありoperator[]ません。仮想演算子がないため、実行時のポリモーフィズムが必要な場合は、名前付き関数が必要になります。その場合、structオペレーター呼び出しを関数呼び出しに適合させる単純なものを作成できます(ただし、ストレージAPIに依存します-コードがポイントのメンバー変数への割り当てによって保存されたデータが変更されると想定している場合は、ポイントを作成する必要がありますテンプレート変数も入力して、これをオーバーライドできるようにします)。

サンプルコードを見ると、ストレージ戦略のテストがあります。こんなことしないで。OOを使用して、ストレージオブジェクトに共通の仮想インターフェイスを実装させるか、(おそらくより良い)テンプレートプログラミングを使用してストレージメカニズムを変更します。

std::vector(最近のC ++標準で)によって行われた保証を見ると、動的ストレージがあり、ポインター演算を使用できるものがありますが、連続したストレージが必要です。一部の値がその場で作成されることを考えると、実装にその制限を課すことはおそらく価値がありませんが、制約自体はの使用を妨げませんoperator[]

于 2010-05-11T09:17:54.570 に答える
2

必要なことは可能ですが、書き込みアクセスも必要なため、結果が少し複雑になることがあります。必要なのは、直接の「ポイント書き込みアクセス」ではなく、一時コピーを返すセッター関数です。これは、コピーがスコープ外になると書き込みを行います。

次のコード フラグメントは、ソリューションの概要を示しています。

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

上記の回答を読んだ後、私はピートの2つのバージョンの回答がoperator[]最善の方法であると判断しました。実行時に型間のモーフィングを処理するために、次の4つのパラメーターを受け取る新しい配列テンプレートクラスを作成しました。

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は、期待される形式の要素をキャッシュするための2次配列であり、GetCachedElement関数は仮想Convert関数を使用して、データがキャッシュに出入りするときにデータを変換します。キャッシュは、少なくとも一度にアクティブにできる同時参照の数と同じ大きさである必要がありますが、私の場合は、必要な変換の数が減るので、大きくするとおそらくメリットがあります。Alskのカーソル実装はおそらくうまく機能しますが、与えられたソリューションは必要なオブジェクトコピーと一時変数が少なく、この場合に重要なわずかに優れたパフォーマンスを提供するはずです。

古いMFCのルックアンドフィールについてSTLファンの皆さんに謝罪します。プロジェクトの残りの部分はMFCであるため、この場合はより理にかなっています。CBigArrayは、関連するスタックオーバーフローの質問の結果であり、これが私の大規模な配列処理の基礎になりました。今日実装を終了し、明日テストしたいと思います。それがすべて私に腹を立てるならば、私はそれに応じてこの投稿を編集します。

于 2010-05-11T16:01:30.123 に答える