3

この質問 (以下のコード) std::async とオブジェクトのコピーにより、コピー/移動の重いライブラリの実装について考えるようになりました。たとえば、これは 5 つのコピーを作成します。値ではなく参照で渡す非同期の場合のように、同時実行性の問題が発生する可能性があるため、対策が難しい場合があります。ムーブ コンストラクターも常に安価であるとは限りません。

私の質問は、参照渡しの方法を見つけるなどの対策は、実際に存在しない問題を解決できるのでしょうか? ライブラリ関数のすべてではないにしても、ほとんどがインライン化されることになると思います。多くの場合、それらは 1 行または 2 行のコードだけです。もちろん、コンパイラは as if ルールに従わなければならないため、出力には 5 つの「delete obj」が含まれます。印刷しないことでデストラクタの副作用を取り除くとしたら、as if ルールはさらに進み、リリース ビルドではコピー/移動が 1 つだけになるのでしょうか?

コンパイラーを混乱させたり、意図せずに副作用を導入して最適化を停止したりしないように、移動コンストラクターを作成するときに注意すべき点はありますか?

class obj {
public:
    int val;
    obj(int a) : val(a) {
        cout << "new obj" << endl;
    }
    ~obj() {
        cout << "delete obj" << endl;
    }
};

void foo(obj a) {
    this_thread::sleep_for(chrono::milliseconds(500));
    cout << a.val << endl;
}

int main(int argc, int **args) {
    obj a(5);
    auto future = async(foo, a);
    future.wait();
    return 0;
}
4

2 に答える 2

5

Does counting moves cause a Heisenberg effect?

No, not really. If you mean that observation of an event changes the event itself, I believe that's a wrong metaphor in this case.

The risk is rather that your observation will miss the event, as if you had never written those side-effects. Although, in general, side-effects shall be taken into consideration by compilers under the "as if" rule (1.9/1), there are a few important situations where the "as if" rule does not apply: that case is when copy/move elision of a temporary object can be performed by the compiler.

The situations when this is allowed are precisely described in Paragraph 12.8/31 of the C++11 Standard:

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the copy/move constructor and/or destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies): [...]

The paragraph then goes on listing what the concrete circumstances are. This is not relevant to answer the question, so I omitted them. You can go look them up.

The important bit is that your side effects may be skipped. If the implementation of std::async() performs some copies or moves, depending on how this is done, the compiler might elide them even if you have some printouts in there, or more generally, even if you have some side-effects.

Thus, you should not rely on side-effects in your copy constructors, move constructors, or destructors as a portable way of counting how many copies or moves are performed within a function: different compilers, different optimization levels and, of course, different implementations of that function might yield different results.

于 2013-02-23T14:05:22.180 に答える
3

必要なコピーは 1 つだけです。これは、左辺値を に渡すためですstd::async。タイプが可動の場合、他のすべては移動可能です。これは、ユーザー定義のデストラクタが暗黙的に定義された移動コンストラクタを抑制するためではありません。async(foo, std::move(a))右辺値を渡すことで、その 1 つのコピーを移動に変えることができます。

5 つのデストラクタが表示された場合、そのうちの 1 つはaそれ自体であり、1 つは の関数パラメータでfooあるため、さらに 3 つのデストラクタが によって内部的に実行されることに注意してくださいstd::async。GCCstd::asyncではあと 2 つしかありません。それより少ない数で実行できるとは思いませんが、実際にはそれ以上は必要ありません。型が移動可能な場合、すべての内部「コピー」は移動する必要があります(実装asyncが本当にひどく書かれていない限り)

いずれにせよ、自分のタイプを安く動かせるようにすれば、3 手でも 10 手でも大した問題にはなりません。

あなたの他の質問への回答で説明されているように、デストラクタの副作用はコピーの省略を防ぎません。

as if ルールはさらに進んで、リリース ビルドで 1 つのコピー/移動だけになるのでしょうか?

いいえ、答えは次のとおりです。

コンパイラーを混乱させたり、意図せずに副作用を導入して最適化を停止したりしないように、移動コンストラクターを作成するときに注意すべき点はありますか?

もありません。副作用は移動を妨げません。移動はオ​​プティマイザーによって行われるのではなく、コードが右辺値からオブジェクトを構築 (または割り当てる) ときに行われます。これは最適化ではなく、C++ 言語のルールとセマンティクスによって管理されます。

このコードでは:

A a;
A b = a;

bオプティマイザーがどれほど巧妙であっても、コンパイラーは に移動コンストラクターを使用できません。コピー コンストラクターを使用する必要があります。これが C++ のルールです。このコードでは:

A a;
A b = std::move(a);

移動コンストラクターがある場合A、コンパイラーはコピーコンストラクターを使用できませんb。最適化がオンになっていない場合でも、移動コンストラクターを使用する必要があります。これは C++ のルールです。

于 2013-02-23T13:53:26.233 に答える