1

回答が得られたので、この質問を読むのを気にしないでください。少し長く、おそらく時間の価値はありません。コードにバグがあり、それが原因で move コンストラクターが呼び出されませんでした。詳細については、回答を確認してください。RVO および NRVO (名前付き戻り値の最適化) は、期待どおりに発生しない呼び出しを説明する可能性があることに注意してください。


この行で move ctor が呼び出されることを期待していますが、代わりに copy ctor が呼び出されます。

Ding d3 = d1 + d2;

Ding クラスには、ユーザー定義の move ctor と on operator+ オーバーロードがあります。move ctor が呼び出されると予想する理由は、operator+ が一時オブジェクト (rvalue 参照) を返すため、移動の最適化が行われる可能性があるからです。

私は C++ の初心者なので、ここに書いていることはすべて間違っている可能性があります。コードは次のとおりです。

// Copied and modified code from here: https://stackoverflow.com/a/3109981
#include <iostream>
#include <cstring>

struct Ding {
    char* data;

    Ding(const char* p) {
        std::cout << " ctor for: " << p << "\n";
        size_t size = strlen(p) + 1;
        data = new char[size];
        memcpy(data, p, size);
    }

    ~Ding() {
        std::cout << " dtor for: " << data << "\n";
        delete[] data;
    }

    Ding(const Ding& that) {
        std::cout << " copy for: " << that.data << "\n";
        size_t size = strlen(that.data) + 1;
        data = new char[size];
        memcpy(data, that.data, size);
    }

    Ding(Ding&& that) {
        std::cout << " MOVE for: " << that.data << "\n";
        data = that.data;
        that.data = nullptr;
    }

    Ding& operator=(Ding that) {
        std::cout << " assignment: " << that.data << "\n";
        std::swap(data, that.data);
        return *this;
    }

    Ding& operator+(const Ding that) const {
        std::cout << " plus for: " << that.data << "\n";
        size_t len_this = strlen(this->data);
        size_t len_that = strlen(that.data);
        char * tmp = new char[len_this + len_that + 1];
        memcpy( tmp,          this->data, len_this);
        memcpy(&tmp[len_this], that.data, len_that + 1);
        Ding * neu = new Ding(tmp);
        return *neu;
    }
};

void print(Ding d) {
    std::cout << "  (print): " << d.data << std::endl;
}

int main(void) {
    std::cout << "putting a Ding on the stack\n";
    Ding d1("jajaja");
    std::cout << "calling print routine\n";
    print(d1);
    std::cout << "putting a second Ding on the stack\n";
    Ding d2("nein");
//  std::cout << "calling print routine\n";
//  print(d2);
    std::cout << "Putting a third Ding on the stack, init from + op ...\n";
    std::cout << "... so expecting so see MOVE ctor used ...\n";
    Ding d3 = d1 + d2;
//  std::cout << "calling print routine\n";
//  print(d3);
    std::cout << "End of main, dtors being called ...\n";
}

VC2010 Express および MinGW (GCC 4.6) のコンパイラ呼び出し (Win7 上) は次のとおりです。

cl /nologo /W4 /EHsc /MD move-sem.cpp
g++ -std=c++0x move-sem.cpp -o move-gcc.exe

どちらのバイナリも同じ出力を生成します (プログラムの最後に破棄する順序はありません)。

putting a Ding on the stack
 ctor for: jajaja
calling print routine
 copy for: jajaja
  (print): jajaja
 dtor for: jajaja
putting a second Ding on the stack
 ctor for: nein
Putting a third Ding on the stack, init from + op ...
... so expecting so see MOVE ctor used ...
 copy for: nein
 plus for: nein
 ctor for: jajajanein
 dtor for: nein
 copy for: jajajanein
End of main, dtors being called ...
 dtor for: jajajanein
 dtor for: nein
 dtor for: jajaja

この長いテキストの後の質問を思い出してDing d3 = d1 + d2;ください。

move ctors が呼び出されない理由については他にも質問があることは承知していますが、その答えをこのケースにマッピングすることはできません。

アップデート

David Rodriguez のコメントに従って、プログラムを次のように変更しました。

--- move-sem.cpp.orig   2012-03-17 17:00:56.901570900 +0100
+++ move-sem.cpp        2012-03-17 17:01:14.016549800 +0100
@@ -36,15 +36,14 @@
                return *this;
        }

-       Ding& operator+(const Ding that) const {
+       Ding operator+(const Ding that) const {
                std::cout << " plus for: " << that.data << "\n";
                size_t len_this = strlen(this->data);
                size_t len_that = strlen(that.data);
                char * tmp = new char[len_this + len_that + 1];
                memcpy( tmp,          this->data, len_this);
                memcpy(&tmp[len_this], that.data, len_that + 1);
-               Ding * neu = new Ding(tmp);
-               return *neu;
+               return tmp;
        }
 };

次に、上記のコンパイラ呼び出しを使用してプログラムを再コンパイルし、1 つのコピー ( copy for: jajajanein) が削除された出力を取得しました。次に、次の行を試しました。

g++ -std=c++0x -fno-elide-constructors move-sem.cpp -o move-gcc.exe

そしてタタ!今、私は仕事でムーブ・クターを見ています!...しかし、現在別のエラーがあると思います。その new の出力にはmove-gcc.exe、dtor 呼び出しが一覧表示されていません。

putting a Ding on the stack
 ctor for: jajaja
calling print routine
 copy for: jajaja
  (print): jajaja
 dtor for: jajaja
putting a second Ding on the stack
 ctor for: nein
Putting a third Ding on the stack, init from + op ...
... so expecting so see MOVE ctor used ...
 copy for: nein
 plus for: nein
 ctor for: jajajanein
 MOVE for: jajajanein
 dtor for:

2 回目の更新

operator+悪いものを次の(おそらく同じように悪い)コードに置き換えました。

Ding& operator+=(const Ding & rhs) {
    std::cout << " op+= for: " << data << " and " << rhs.data << "\n";
    size_t len_this = strlen(this->data);
    size_t len_that = strlen(rhs.data);
    char * buf = new char[len_this + len_that + 1];
    memcpy( buf,         this->data, len_this);
    memcpy(&buf[len_this], rhs.data, len_that + 1);
    delete[] data;
    data = buf;
    return *this;
}

Ding operator+(const Ding & rhs) const {
    Ding temp(*this);
    temp += rhs;
    return temp;
}

また、デストラクタから次の行を削除したところ、プログラムが異常終了するのを防ぎました。

std::cout << " dtor for: " << data << "\n";

MSVC および を使用してコンパイルすると、移動コンストラクタが呼び出されるようになりましたg++ -std=c++0x -fno-elide-constructors

4

3 に答える 3

2
    Ding * neu = new Ding(tmp);
    return *neu;

これは間違っています。を動的に割り当ててDingから、そのコピーを強制しています。Dingは動的に割り当てられるため、リークしているため、その寿命は return ステートメントを超えて延長され、コンパイラはそこから移動できません。一時的なものを返していないことに注意してください。

それを次のように変更します。

    return Ding(tmp);

あるいは:

    return tmp;

a を取るコンストラクターはexplicitconst char*ではないため、コンパイラーはそれを使用して新しいオブジェクトを作成します。どちらの場合も、一時の有効期間はステートメントを超えて延長されず、コンパイラは移動します。Dingreturn

(この回答は、返されたオブジェクトから へのコピーが省略されていることを理解していることを前提としていますd3。それが移動を期待していた場合、コンパイラはより良いことを行いました:操作を完全に回避します)。

EDIT DeadMGは正規のフォームを作成しましたが、エラーが含まれているため、フォローアップします。

演算子のオーバーロードについては言いたいことがたくさんありますが、一般的な推奨事項 (私が示して従うもの) はoperatorX=、メンバー関数として実装し (左辺に適用される操作です)、次にoperator+フリー関数として実装することです。前者。C++11 では、次のようになります。

class X {
   X& operator+=( X const & ); // we do not modify the rhs internally
};
X operator+( X lhs, X const & rhs ) {
  lhs += rhs;                  // reuse implementation
  return lhs;
}

注意すべき点:operator+は自由な関数であるため、型に関して対称です。右辺で発生する可能性のあるすべての暗黙的な変換は、lhs でも利用できます。あなたの特定のケースでDingは、 は から暗黙的に変換可能ですconst char*。つまり、自由な関数operator+を持つことで、次のように記述できます。

Ding d( "A" );
const char* str = "B";
d + d;     // no conversions
d + str;   // conversion on the right hand side
str + d;   // conversion on the left hand side

public メンバー関数として定義することによりoperator+=、単一の実装を作成する必要があり、その実装は再利用できるため、1 つのコストで 2 つの演算子 (および些細な 3 つの余分なコード行) を取得できます。

値による引数と値による戻り。これにより、引数が一時的なものである場合、コンパイラはパラメーターへのコピーを省略できます。移動コンストラクターがあるため、内部コピーもありません。引数は変更され、戻りオブジェクトに移動されます(この 2 番目のコピーは省略できません)。

少し前に、ここで演算子のオーバーロードについてもう少し書きました...最適化(移動)を明示的に処理していませんが、C++03バージョンを処理する他の投稿があります。そこから C++11 の機能まで、空白を埋めることができるはずです。

于 2012-03-17T15:47:59.717 に答える
1

動かすものは何もありません。

d1またはから移動することはできませんd2。それはそれらを破壊するからです。また、これは参照であるため、の戻り値を移動することはできませんoperator+

于 2012-03-17T15:48:51.790 に答える
1

正しい加算演算子を記述していません。正規形は

Ding operator+(Ding other) const {
    other += this;
    return other; 
    // return std::move(other) if you're on MSVC 
    // which sometimes doesn't do this properly
}
Ding& operator+=(const Ding& other);

コードには、不適切な演算子のオーバーロードが原因で発生するメモリ リークなど、多くの問題があります。

また、期待される出力に対する RVO と NRVO の潜在的な影響を忘れないでください。

于 2012-03-17T16:24:31.100 に答える