3

昨日書いたこの質問を参考にして、この質問を書いています。少し文書化した後、私がやりたかったこと (そして私が可能だと信じていたこと) は、まったく不可能ではないにしても、ほぼ不可能であることが明らかになりました。それを実装するにはいくつかの方法があります。私は経験豊富なプログラマーではないので、どの方法を選択するかお尋ねします。私の問題をもう一度説明しますが、今はいくつかの解決策を検討する必要があります。

必要なもの

Matrix クラスがあり、クラスの使用法が非常に直感的になるように、行列間の乗算を実装したいと考えています。

Matrix a(5,2);
a(4,1) = 6 ;
a(3,1) = 9.4 ;           
...                   // And so on ...

Matrix b(2,9);
b(0,2) = 3;
...                   // And so on ...

// After a while
Matrix i = a * b;

昨日持っていたもの

現時点では、2 つの演算子をオーバーロードしてoperator*おりoperator=、昨日の夜までは次のように定義されていました。

Matrix& operator*(Matrix& m);
Matrix& operator=(Matrix& m);

operator* は、ヒープ上で新しい Matrix オブジェクト ( Matrix return = new Matrix(...)) をインスタンス化し、値を設定してから、次のようにします。

return *result;

私が今日持っているもの

議論の後、ユーザーがあらゆるタイプのポインターに煩わされるのを避け、使用法を変更しないようにするために、「別の方法」で実装することにしました。「別の方法」は、 operator* の戻り値を値で渡すことです。

Matrix operator*(Matrix& m);
Matrix& operator=(Matrix& m);

operator*returnはスタック上でインスタンス化し、値を設定してからオブジェクトを返します。

このアプローチには問題があります。うまくいきません。operator= は Matrix& を期待し、operator* は Matrix を返します。さらに、このアプローチは別の理由で私にはあまり良く見えません:私は行列を扱っていますが、それは非常に大きくなる可能性があり、このライブラリの目的は1)私のプロジェクトにとって十分に良い2)高速であるため、おそらく合格することでした値によるオプションは使用できません。

私が調査したソリューション

さて、前の議論の提案に従って、スマート ポインターに関するいくつかの資料を読みました。それらは見栄えがしますが、問題を解決する方法をまだ理解できません。それらはメモリの解放とポインタのコピーを処理しますが、私は基本的に参照を使用しているため、適切な選択ではないようです。しかし、私は間違っているかもしれません。

おそらく唯一の解決策は値渡しであり、効率と優れたインターフェースの両方を得ることができないのかもしれません。繰り返しますが、あなたは専門家ですので、あなたの意見を知りたいです。

4

5 に答える 5

9

あなたが抱えている問題は、式が一時a * bオブジェクトを作成することです.C++では、一時オブジェクトが非定数参照にバインドすることは許可されていません.これはあなたが取るものです. 次のように変更した場合:Matrix& operator=(Matrix& m)

Matrix& operator=(Matrix const& m);

コードがコンパイルされるはずです。コンパイル可能なコードを生成する明らかな利点:) と同様に、を追加constすると、引数を変更しないことが呼び出し元に伝えられますm。これは役立つ情報になる可能性があります。

に対しても同じことを行う必要がありますoperator*()

Matrix operator*(Matrix const& m) const;

[編集:最後の追加は、メソッドが乗算の左側のオブジェクトであるconstを変更しないことを約束していることを示しています。これは、次のような式に対処するために必要です-- 部分式は一時的な を作成し、最後のなしではバインドしません。コメントでこれを指摘してくれた Greg Rogers に感謝します。]*thisa * b * ca * bconst

PS C++ が非定数参照への一時的なバインドを許可しない理由は、(名前が示すように) 一時的なものが非常に短い時間しか存在しないためであり、ほとんどの場合、それらを変更しようとするのは間違いです。 .

于 2009-01-25T10:18:49.163 に答える
9

Scott Meyers による効果的な C++を実際に読む必要があります。それに関する優れたトピックがあります。すでに述べたように、 と の最良のoperator=署名operator*

Matrix& operator=(Matrix const& m);
Matrix operator*(Matrix const& m) const;

しかし、乗算コードを実装する必要があると言わざるを得ません

Matrix& operator*=(Matrix const& m);

そしてそれを再利用するだけですoperator*

Matrix operator*(Matrix const &m) const {
    return Matrix(*this) *= m;
}

そうすれば、ユーザーは必要なときに新しい行列を作成せずに乗算できます。もちろん、このコードが機能するには、コピーコンストラクターも必要です:)

于 2009-01-25T10:27:25.387 に答える
3

注: Vadims の提案から始めます。以下の議論は、非常に小さな行列のみについて話している場合、たとえば、3x3 または 4x4 の行列に限定している場合は意味がありません。また、私があなたに多くのアイデアを詰め込もうとしていないことを願っています:)

マトリックスは重いオブジェクトになる可能性があるため、ディープ コピーは絶対に避ける必要があります。スマート ポインターはそれを実装するための 1 つのユーティリティですが、すぐに問題を解決できるわけではありません。

あなたのシナリオでは、2 つの一般的なアプローチがあります。どちらの場合も、任意のコピー ( などa=b) は、参照のみをコピーし、参照カウンターをインクリメントします (これは、スマート ポインターが実行できることです)。

コピー オン ライトを使用すると、変更が行われるまでディープ コピーが遅延されます。void Matrix.TransFormMe()たとえば、 onのようなメンバー関数を呼び出すbと、実際のデータが 2 つのオブジェクト (a と b) によって参照されていることがわかり、変換を行う前にディープ コピーが作成されます。

最終的な効果として、マトリックス クラスは「通常の」オブジェクトのように動作しますが、実際に作成されるディープ コピーの数は劇的に減少します。

もう 1 つのアプローチは、API 自体が既存のオブジェクトを変更しない不変オブジェクトです。変更すると新しいオブジェクトが作成されます。したがって、void TransformMe()' member transforming the contained matrix, Matrix contains only aMatrix GetTransformed()`メンバーの代わりに、データのコピーを返します。

どちらの方法が優れているかは、実際のデータによって異なります。MFC ではCStringコピー オン ライトであり、.NET では aStringは不変です。StringBuilder多くの場合、不変クラスには、多くの順次変更のコピーを回避するビルダー クラス ( など) が必要です。Copy-On-Write オブジェクトは、API でどのメンバーが内部メンバーを変更し、どのメンバーがコピーを返すかが明確になるように、慎重に設計する必要があります。

行列の場合、その場で行列を変更できる (つまり、アルゴリズム自体はコピーを必要としない) アルゴリズムが多数あるため、コピー オン ライトの方が優れたソリューションである可能性があります。

ブーストスマートポインターの上にコピーオンライトポインターを構築しようとしたことがありますが、スレッドの問題などを把握するために触れていません。擬似コードは次のようになります。

class CowPtr<T>
{
     refcounting_ptr<T> m_actualData;
   public:
     void MakeUnique()
     {
        if (m_actualData.refcount() > 1)
           m_actualData = m_actualData.DeepCopy();
     }
     // ...remaining smart pointer interface...
}

class MatrixData // not visible to user
{
  std::vector<...> myActualMatrixData;
}

class Matrix
{
  CowPtr<MatrixData> m_ptr; // the simple reference that will be copied on assignment

  double operator()(int row, int col)  const
  {  // a non-modifying member. 
     return m_ptr->GetElement(row, col);
  }

  void Transform()
  {
    m_ptr.MakeUnique(); // we are going to modify the data, so make sure 
                        // we don't modify other references to the same MatrixData
    m_ptr->Transform();
  }
}
于 2009-01-25T10:58:31.783 に答える
3

… (必要に応じて) すべてが呼び出されるため、const 以外はすべて次のようになります。

void lupp();

これにより、キャッシュされたLUおよびが更新されますP。同じことがget_inverse()その呼び出しlupp()を表し、 も設定しますMatrix* Matrix::inverse。これにより、次の問題が発生します。

Matrix& operator=(Matrix const& m);
Matrix operator*(Matrix const& m);

技術。

それがどのように問題を引き起こすのか説明してください。通常はそうすべきではありません。さらに、メンバー変数を使用して一時的な結果をキャッシュする場合は、それらをmutable. constその後、オブジェクトでも変更できます。

于 2009-01-25T11:00:16.210 に答える
0

はい、あなたの提案はどちらも良いです。非 const 参照による一時オブジェクトの問題を知らなかったことを認めます。しかし、私の Matrix クラスには、LU 因数分解 (ガウス消去法) を取得するための機能も含まれています。

const Matrix& get_inverse();
const Matrix& get_l();
const Matrix& get_u();
const Matrix& get_p();

彼らはすべて呼び出しているので、すべてですconst(必要な場合):

void lupp();

これにより、キャッシュされた L、U、および P が更新されます。同じget_inverse()ことが lupp() の呼び出しを表し、 も設定しますMatrix* Matrix::inverse。これにより、次の問題が発生します。

Matrix& operator=(Matrix const& m);
Matrix operator*(Matrix const& m);

技術。

于 2009-01-25T10:51:18.303 に答える