2016-04-23 6 views
0

ライブラリでは、クラスCのファクトリメソッドで作成されたクラスAがあります。返されたオブジェクトは、静的メンバ私の問題は、戻り値の最適化は、少なくともVisual Studioでは、XCode(clang)で期待どおりに動作しないようです。何が起こるかは、の前にクラスAのdtorがと呼ばれ、定義された移動割り当て演算子が使用されるということです。 d-torでは、Aのメモリが解放され、その後はデータが無効になります。ファクトリメソッドと静的変数の割り当てからの戻り値の最適化(Visual Studio)

class A { 
public: 
    A(); 
    A(const A &other); 
    A(const A &&other); 

    A& operator = (const A &other); 
    A& operator = (const A &&other); 
}; 

class B { 
private: 
    static A _a; 
    struct Initializer { 
    Initializer(); 
    }; 
    static Initializer _init; 
}; 

B::Initializer::Initializer() { 
    C c; 
    _a = c.factoryMethod(); 
} 

B::Initializer B::_init; 

ファクトリメソッドは非常に簡単です:Aの

A C::factoryMethod() { 
    A a; 
    .... 
    return a; 
} 

return aでコピーコンストラクタは、D-TORが続く、と呼ばれています。その後、move演算子がトリガされます(Bの_aへの結果の代入時)。

これは、RVOの想定とはかなり反対です。コピーが存在してはいけません。したがって、Bが削除された場合を除いて、破壊されるべきではありません。それで、なぜ期待通りに動かないのですか?

アップデートは、いくつかの試行錯誤の後、私は、Visual StudioとXCodeの中で両方の作品解決策を見つけました。しかし、この質問は、これがどのように機能するのか(なぜ私の場合はそうでないのか)を理解することなので、私はまだ良い答えに興味があります。私が問題を解決するためにしたのは、コピーコンストラクタを完全に削除することだったので、実際には呼び出されませんでした。

2つのコンパイラの動作は、まだ異なります。 VSは移動コンストラクタを使用しますが、XCodeは移動コンストラクタを使用しませんが、何があっても定義されている必要があります。もう1つの奇妙な効果は、私は今VSで2つの移動を持っていることです(移動コンストラクタとその後、静的メンバーvarに割り当てるときの移動割り当て)。 XCodeには移動割り当てのみがあります。

+3

まず、 'A(const && other);'は有効な構文ではないため、コンパイルしないでください。第2に、移動コンストラクタは通常、非const rvalue参照をとります。あなたはたぶん 'A(A && other);を意味しています;代入演算子と同様です。 –

+1

[私のために働く](http://rextester.com/KUY47394)と思われます。私は問題を見ない。 –

+0

はい、構文が間違っていました(私はそれを更新しました)。しかし、あなたの実際の例:私が書いたように、コンパイラに依存するようです。はい、XCode(clang)で意図したとおりに動作しますが、問題を引き起こすのはVisual Studioです。 'return a'は移動コンストラクタではなくコピーコンストラクタで終わり、同じ場所からのdtorコールが続きます。戻り値が静的varに代入されると、移動割り当てが続きます。それはクレイジーです。 Btw。 constかどうかは違いはありません。 –

答えて

2
static A _a; 

これは静的メンバー変数です。したがって、mainより前にが初期化されます。すでにオブジェクトを保持しています。したがって、_a = c.factoryMethod();はコピーを削除できません。

コピーエリミディは、を初期化するときにのみ発生する可能性があります。変数。既に初期化されているので、既に初期化された変数にコピーする必要があります。

このコピーは、operator=への呼び出しによって行われます。

いいえ、あなたの_initオブジェクトも静的であるという事実は役に立ちません。 C++では、C++ファイル内の静的オブジェクトの初期化の順序が定義されており、その順序は宣言の順序です。したがって、最初にB::_aが初期化され、次にB::_initに初期化されます。したがって、B::_initが実行されると、B::_aはすでに初期化されています。まだ生涯は始まっていないオブジェクトに割り当てることができないので、順序を逆にすることは役に立ちません。

実際にelisionを使用して複雑な初期化を行う場合は、静的メンバ変数を.iniファイルに宣言するときに正しく実行する必要があります。cppのファイル:

A B::_a = []() { 
    C c; 
    return c.factoryMethod(); 
}() 

あなたは覚えている他の事はエリジオンはオプションであるということです。コンパイラにはにはがありません。コンパイラに強制する方法はありません。

特に名前付き変数の場合。特定のケースでelisionを強制するC++ 17の提案でさえ、名前のない戻り値に対してのみ強制します。提案されたC++ 17の機能では、ラムダで適切な初期化を行うと、最悪のケースではコンストラクタ呼び出しを1つ移動することが保証されます。

+0

私は本当にこれがそれだと思っていましたが、残念ながら、解決策もありません。まず、イゴールの小さな例が働いています。第二に、それはclangで動作します。両方とも、コピーelisionは初期化時にのみ発生するという主張に反します。そして第三に、lambdaを使っても、コピーメソッドのコンストラクタは、私のファクトリメソッドで 'return a'を呼び出すときに呼び出されます。 –

+0

@MikeLischke:答えの追加をご覧ください。 –

+0

オプションのRVOは、本当に問題になります。クラスAでいくつかのクラスインスタンスを削除する必要があります。コピーインスタンスが呼び出された場合はコピーが無効になります。移動コンストラクタのconstは、VSとXCodeのどちらでも、btwの検索に影響しません。しかし、それは重要な副作用を持っています:移動するメンバーは失敗し、したがってあなたはコピーコンストラクタと同じ状況になります。だから確かに、constはしないでください:-) –

関連する問題