1

次のコードは、学習演習として、Windows デスクトップ用の Visual Studio 2012 Express でコンパイルおよび実行されました。

#include <cstdio>

class X
{
public:
    X()  { printf("default constructed\n"); }
    ~X() { printf("destructed\n");}
    X(const X&) { printf("copy constructed\n"); }
    X(X&&) { printf("move constructed\n"); }
    X & operator= (const X &) { printf("copy assignment operator\n"); }
};

X A() {
    X x;
    return x;
}

int main() {
    {
        A();
    }
    std::getchar();
}

コンパイラの最適化を無効にしてコンパイルした場合 (/Od)、結果の出力は、デストラクタが 2 回呼び出されたことを示します。オブジェクトが 1 つしか構築されていない場合、これは問題です。デストラクタが 2 回呼び出されるのはなぜですか? クラスが独自のリソースを管理している場合、これは問題になりませんか?

default constructed
move constructed
destructed
destructed   <<< Unexpected call 

出力を説明するためにいくつかの実験を試みましたが、最終的にこれらは有用な説明につながりませんでした。

実験 1: 最適化を有効にして (/O1 または /O2) 同じコードをコンパイルすると、結果の出力は次のようになります。

default constructed
destructed

これは、名前付き戻り値の最適化がムーブ コンストラクターの呼び出しを省略し、根本的な問題を隠したことを示しています。

実験 2: 最適化を無効にし、移動コンストラクターをコメントアウトしました。生成された出力は、私が期待したものでした。

default constructed
copy constructed
destructed
destructed
4

3 に答える 3

7

オブジェクトが移動操作のソースである場合でも、オブジェクトは破棄されることに注意してください。そのため、移動元は、破壊されても所有しなくなったリソースを解放しないような状態にする必要があります (別のオブジェクトに移動されたため)。たとえば、ソース オブジェクト内のすべての生のポインター (移動によって構築されたオブジェクトによって所有されるようになる) は、NULL に設定する必要があります。

于 2012-10-05T05:19:54.823 に答える
3

A の X は、スコープ外になると破棄されます。

A は、別のインスタンスである一時オブジェクト (ムーブ コンストラクターによって X から構築される) を返します。これは、呼び出し元のスコープで破棄されます。これにより、(一時的に) デストラクタが再度呼び出されます。

移動コンストラクターが選択されたのは、X が直後に破棄されることをコンパイラーが検出したためです。この方法を使用するには、移動先によって引き継がれたデータがデストラクタによって無効にされないように、移動コンストラクタで元のオブジェクトのデータを無効化またはリセットする必要があります。

右辺値を値で渡す場合、または関数から値で何かを返す場合、コンパイラは最初にコピーを除外するオプションを取得します。コピーが省略されていないが、問題の型に移動コンストラクターがある場合、コンパイラーは移動コンストラクターを使用する必要があります。

http://cpp-next.com/archive/2009/09/move-it-with-rvalue-references/

一時オブジェクトが作成されたスコープから出ると、一時オブジェクトは破棄されます。参照が一時オブジェクトにバインドされている場合、一時オブジェクトは、制御フローの中断によって以前に破棄されない限り、参照がスコープ外に渡されたときに破棄されます。

http://publib.boulder.ibm.com/infocenter/comphelp/v8v101/index.jsp?topic=%2Fcom.ibm.xlcpp8a.doc%2Flanguage%2Fref%2Fcplr382.htm

RVO は、最適化されていないバージョンとは異なる動作を生成する可能性があります。

戻り値の最適化、または単に RVO は、関数の戻り値を保持するために作成された一時オブジェクトを削除することを含む、コンパイラの最適化手法です。[1] C++ では、結果として得られるプログラムの観察可能な動作を変更できることは特に注目に値します[2]。

http://en.wikipedia.org/wiki/Return_value_optimization

于 2012-10-05T04:42:26.927 に答える
2

Michael と jspcal の回答は正確ですが、私の質問の核心には答えていませんでした。これが、2 つのデストラクタ呼び出しが行われた理由です。期待していたのは1つだけです。

答えは、関数 A() が一時オブジェクトを返すことです。いつも。これは関数の戻り値がどのように機能するかであり、移動セマンティクスはこの事実とは関係ありません。Michael と jspcal は、私がそのような基本的な事実を見逃していないと思っていたのでしょう。「移動」という用語を「スワップ」の概念と同一視しました。スワップすると、オブジェクトは構築および破棄されません。したがって、デストラクタの呼び出しは 1 回だけだと思っていました。

返されたオブジェクトは構築および破棄する必要があるため、2 番目のデストラクタ呼び出し (および 2 番目のコンストラクタ呼び出し) が行われました。

現在、実行するために選択された実際のコンストラクターは、クラス定義で提供されているものによって異なります。move-constructor が利用可能な場合、それが呼び出されます。それ以外の場合は、コピー コンストラクターが呼び出されます。

于 2012-10-08T08:06:48.023 に答える