2

以下のコードを検討してください。

// Consider that MyObject receives three integers in it's constructor.
MyObject createObject()
{
    MyObject result(1, 2, 3);
    return result;
}

私の知る限り、(この関数のように) C++ でオブジェクトを返すと、コンパイラはクライアントのスタックに新しいオブジェクトを (ローカル変数として) 作成します。この例では、パラメーターとしてMyObjectコピー コンストラクターを使用して作成される新しいオブジェクトになりresultます。これは、マシンの冗長処理を意味します。オブジェクトresultがスタックに作成され、次に 2 番目のオブジェクトが作成されて返されます。これを回避する別の方法は、オブジェクトを動的に (スタックではなく) 作成し、そのポインターを返すことです。ただし、メモリ リークが発生したり、クライアントが使用後にオブジェクトを強制的に削除したりする可能性があります。したがって、私が見つけた解決策は、次のようにスマート ポインターを使用することでした。

// Consider that MyObject receives three integers in it's constructor.
boost::smart_ptr<MyObject> createObject()
{
    boost::smart_ptr<MyObject> result(new MyObject(1, 2, 3));
    return result;
}

これは機能しますが、スマート ポインターは依然として再作成されるオブジェクトです (基本的にはポインターのみを保持するため、コストが低くても)。これをもっと簡単に行う方法があるかどうかを推測していました。または、この作業などを最適化する方法がコンパイラーによってまだ実装されていない場合。私がやりたいことをするためにコンパイラに言う構文はありますか?

4

3 に答える 3

6

最初にコードのプロファイルを作成します。コピーによるオーバーヘッドがないことは 99% 確信しています。

NRVO は存在し、おそらくこの場合に効果を発揮するでしょう。

コピーの省略がなければ、コレクションを返すstd::vectorことは大きな問題を引き起こすでしょう。

C++ でオブジェクトを返すと (この関数のように)、コンパイラはクライアントのスタックに新しいオブジェクトを (ローカル変数として) 作成します。

あまり。余分なコピーを防ぐために、呼び出しコンテキストで直接作成される可能性があります。

于 2012-07-26T04:39:32.007 に答える
2

C++11 を使用する場合は、「移動セマンティクス」を使用できます。最初に例を挙げて説明しましょう。

class expensive {
public:
     expensive() : n(0), V(NULL) {}
     expensive(int _n) {
         n = _n;
         V = new int[_n];
         for (int i = 0; i < n; ++i) V[i] = i*i;
     }

     expensive(const expensive& rhs) { // copy constructor
          n = rhs.n;
          V = new int[n];
          for (int i = 0; i < n; ++i) V[i] = rhs.V[i];
     }
     expensive(expensive&& rhs) { //move constructor
          n = rhs.n;
          V = rhs.V;
          rhs.n = -1;
          rhs.V = NULL;
          printf("Moving\n");
     }
     ~expensive() {
          if (n == -1) printf("Destroying 'moved' instance\n");
          if (V) delete [] V;
     }
private:
     int *V;
     int n;
};
expensive f(int x) {
    expensive temp(50);
    expensive temp2(temp); // Copy temp to temp2, both are valid now
    expensive temp3(std::move(temp)); // Move temp to temp3, temp is not valid anymore
    return std::move(temp2); // move temp2 to the return 
}
int main() {
    expensive E = f(30);
    return 0;
}

このプログラムの出力は次のとおりです。

Moving
Moving
Destroying 'moved' instance
Destroying 'moved' instance

通常の STL コンテナーはすべて、移動セマンティクスをサポートしています。また、std::swap もそれを使用します (したがって、std::sort も同様です)。

編集: 前述のように、NRVO が使用されている場合、最後の std::move は不要です。もっと複雑な例を考えてみましょう。これはおもちゃの例ですが、私の要点を示しているはずです。

class expensive {
    ... // Same definition as before
}
expensive g(int x) {
    vector<expensive> V;
    V.reserve(2*x);
    for (int i = 1; i <= x; ++i)
         V.push_back(expensive(i)); // Here the objects are moved to the vector
    for (int i = 1; i <= x; ++i)
         V.emplace_back(i); // Here the objects are constructed directly in the vector
    return std::move(V[x/2]); // V[x/2] is moved to the answer
}
int main() {
    expensive x(g(2)); // 3 objects should be moved, 2 in push_back and one on the return
    return 0;
}
于 2012-07-26T04:57:59.007 に答える
1

ソース: http://blogs.msdn.com/b/slippman/archive/2004/02/03/66739.aspx

コンパイラ オプティマイザーは、何が起こっているのかを理解するのに十分なほどスマートであるため、最適化するときに内部的に呼び出しを次のように書き換えます。

void CallingFunction()
{
    // Call to createObject() gets eliminated through transformation
    // since in this case it's only code was the initialization
    MyObject obj(1, 2, 3);

    // ... do stuff
}
于 2012-07-26T05:18:33.347 に答える