5

次の代入およびコピー移動コンストラクターは最も効率的ですか? 他の方法がある場合は教えてください。つまり、std::swap とは何ですか? 以下のコードでは、コピーコンストラクターを介して代入を呼び出すことは安全ですか?

#include <iostream>
#include <functional>
#include <algorithm>
#include <utility>

using std::cout;
using std::cin;
using std::endl;
using std::bind;


class Widget
{

public:

    Widget(int length)
        :length_(length),
        data_(new int[length])
    {
        cout<<__FUNCTION__<<"("<<length<<")"<<endl;
    }

    ~Widget()
    {
        cout<<endl<<__FUNCTION__<<"()"<<endl;
        if (data_)
        {
            cout<<"deleting source"<<endl;
        } 
        else
        {
            cout<<"deleting Moved object"<<endl;
        }

        cout<<endl<<endl;
    }

    Widget(const Widget& other)
        :length_(other.length_),
        data_(new int[length_])
    {
        cout<<__FUNCTION__<<"(const Widget& other)"<<endl;
        std::copy(other.data_,other.data_ + length_,data_);
    }

    Widget(Widget&& other)
/*
        :length_(other.length_),
        data_(new int[length_])*/
    {
        cout<<__FUNCTION__<<"(Widget&& other)"<<endl;
        length_ = 0;
        data_ = nullptr;
        std::swap(length_,other.length_);
        std::swap(data_,other.data_);
    }

    Widget& operator = (Widget&& other)
    {
        cout<<__FUNCTION__<<"(Widget&& other)"<<endl;

        std::swap(length_,other.length_);
        std::swap(data_,other.data_);

        return *this;
    }

    Widget& operator = (const Widget& other)
    {
        cout<<__FUNCTION__<<"(const Widget& other)"<<endl;
        Widget tem(other);
        std::swap(length_,tem.length_);
        std::swap(data_,tem.data_);

        return *this;
    }
    int length()
    {
        return length_;
    }

private:

    int length_;
    int* data_;
};



int main()
{
    {
        Widget w1(1);
        Widget w2(std::move(Widget(2)));

        w1 = std::move(w2);
    }


    cout<<"ENTER"<<endl;
    cin.get();
    return 0;
}
4

3 に答える 3

7

効率の観点から見ると問題ないように見えますが、非常に多くの重複コードが含まれています。私はしたい

  • swap()クラスの演算子を実装します。
  • 初期化length_data_それらが宣言されている場所。
  • 可能な限り、他の操作の観点から操作を実装します。

とにかく生の配列を扱っているので、代わりに使用したい場合あります。一部のコンパイラはそれを行いますが、おそらくすべてではありません...std::memcpystd::copy

コードの重複除去バージョンを次に示します。の 2 つのインスタンスがどのように交換されるかを知る必要がある場所は1 つだけであることに注意してください。Widgetそして、特定のサイズのウィジェットを割り当てる方法を知っている場所は1 つだけです。

編集:通常、非プリミティブメンバーがある場合に備えて、引数依存のルックアップを使用してスワップを検索することも必要です。

編集:代入演算子に引数を値でとらせるという@Philippの提案を統合しました。そうすることで、移動代入演算子とコピー代入演算子の両方として機能します。ムーブの場合、テンポラリを渡してもコピーされないわけではありません。これは、コピー コンストラクターではなくムーブ コンストラクターが引数の受け渡しに使用されるためです。

編集: C++11 では、以前のバージョンの標準との互換性のために、右辺値で非コスト メンバーを呼び出すことができます。これにより、奇妙なコードのようなWidget(...) = someWidgetコンパイルが可能になります。宣言の後に置くことで左辺operator=値を必要とすることで、それを防ぐことができます。ただし、その制限がなくてもコードは正しいことに注意してください。それでも、それは良い考えのように思われるので、追加しました。this&

編集: Guillaume Papin が指摘したように、デストラクタはdelete[]plain の代わりに使用する必要がありdeleteます。C++ 標準では、 経由で割り当てられたメモリを 経由new []で削除することが義務付けられています。delete []つまり、new' andnew []` が異なるヒープを使用できるようになっています。

class Widget
{
public:
    Widget(int length)
        :length_(length)
        ,data_(new int[length])
    {}

    ~Widget()
    {
        delete[] data_;
    }

    Widget(const Widget& other)
        :Widget(other.length_)
    {
        std::copy(other.data_, other.data_ + length_, data_);
    }

    Widget(Widget&& other)
    {
        swap(*this, other);
    }

    Widget& operator= (Widget other) &
    {
        swap(*this, other);
        return *this;
    }

    int length() const
    {
        return length_;
    }

private:
    friend void swap(Widget& a, Widget& b);

    int length_ = 0;
    int* data_ = nullptr;
};

void swap(Widget& a, Widget& b) {
    using std::swap;
    swap(a.length_, b.length_);
    swap(a.data_, b.data_);
}
于 2012-09-29T14:24:10.660 に答える
0

移動コンストラクターでは、それほど多くのスワップや割り当ては必要ありません。これ:

Widget(Widget&& other) :
    length( other.length_ ), data( other.data_ )
{
    other.length_ = 0;
    other.data_ = nullptr;
}

移動コンストラクターの最小作業を実行します:合計4つの割り当て。ご使用のバージョンは、swap()の呼び出しで8を数えました。

ムーブ代入は問題ありませんが、両方のケースをカバーするために、1つのoperator =()を記述することを検討することをお勧めします。

Widget &operator=( Widget other ) {
    delete data_;
    data_ = other.data_;
    other.data_ = nullptr;
    length_ = other.length_;
    other.length_ = 0;
    return *this;
}

これは、2回移動できるという点で、バージョンよりもわずかに効率が低くなります。

于 2012-10-01T13:25:08.023 に答える
0

答えは、上記のコメントにある@Abdulrhmanの苦情への対応です。割り当てのいくつかの(あいまいな)シーケンスで失敗します。その方が読みやすいので、別の回答に入れます。

苦情はそれでした

Widget w(2);
w = Widget(1) = std::move(w);

クラッシュします。これが私が得た出力です

Widget w(2);
w.data()[0] = 0xDEAD; w.data()[1] = 0xBEEF;
w = Widget(1) = std::move(w);
std::cerr << std::hex << w.data()[0] << w.data()[1] << std::endl;

Widgetコンストラクタ、デストラクタ、および代入演算子の呼び出しをログに記録するためにいくつかのコードが追加されています。インターリーブは、それらの呼び出しがどこから来たのかについてのコメントです

w is constructed
0x7fff619c36c0: [constructor] allocated 2@0x1043dff80
temporary Widget(1) is constructed
0x7fff619c37c0: [constructor] allocated 1@0x1043e0180
first (right) assignment operator argument is constructed. w is empty afterwards!
0x7fff619c3800: [default constructor] empty
0x7fff619c3800: [move constructor] stealing 2@0x1043dff80 from 0x7fff619c36c0, replacing with 0@0x0
first assignment operator does it's job, i.e. moves from by-value argument.
0x7fff619c37c0: [assignment] stealing 2@0x1043dff80 from 0x7fff619c3800, replacing with 1@0x1043e0180
second (left) assignment operator arguments is constructed
0x7fff619c3780: [constructor] allocated 2@0x1043e0280
0x7fff619c3780: [copy constructor] copying 2@0x1043dff80 from 0x7fff619c37c0
second assignment operator does it's job, i.e. moves from by-value argument
0x7fff619c36c0: [assignment] stealing 2@0x1043e0280 from 0x7fff619c3780, replacing with 0@0x0
second assingment operator's by-value argument is destructed
0x7fff619c3780: [destructor] deleting 0@0x0
first assignment operator's by-value argument is destructed
0x7fff619c3800: [destructor] deleting 1@0x1043e0180
temporary created as Widget(1) is destructed.
0x7fff619c37c0: [destructor] deleting 2@0x1043dff80
data contains in "w" after assignments.
deadbeef
finally, "w" is destructed.
0x7fff619c36c0: [destructor] deleting 2@0x1043e0280

そこに問題は見られず、これをclangでコンパイルし-faddress-sanitizer, -fcatch-undefined-behaviourても文句はありません。

ただし、2 番目の割り当て (左側の=演算子) は移動ではなくコピーすることに注意してください。これは、最初 (右側) の代入演算子が左辺値参照を返すためです。

于 2012-10-01T10:41:35.897 に答える