28

私はC++ でムーブ コンストラクターの可能性を探ってきましたが、以下のような例で、この機能を利用する方法を知りたいと思っていました。次のコードを検討してください。

template<unsigned int N>
class Foo {
public:
    Foo() {
        for (int i = 0; i < N; ++i) _nums[i] = 0;
    }

    Foo(const Foo<N>& other) {
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) {
        // ??? How can we take advantage of move constructors here?
    }

    // ... other methods and members

    virtual ~Foo() { /* no action required */ }

private:
    int _nums[N];
};

Foo<5> bar() {
    Foo<5> result;
    // Do stuff with 'result'
    return result;
}

int main() {
    Foo<5> foo(bar());
    // ...
    return 0;
}

上記の例で、(MSVC++ 2011 を使用して) プログラムをトレースすると、 のFoo<N>::Foo(Foo<N>&&)構築時に が呼び出されていることがわかりfooます。これは望ましい動作です。ただし、 がない場合はFoo<N>::Foo(Foo<N>&&)Foo<N>::Foo(const Foo<N>&)代わりに , が呼び出され、冗長なコピー操作が行われます。

私の質問は、コードに記載されているように、静的に割り当てられた単純な配列を使用しているこの特定の例では、移動コンストラクターを使用してこの冗長なコピーを回避する方法はありますか?

4

4 に答える 4

26

まず、コピー/移動コンストラクター、代入演算子、またはデストラクタを作成するのを助けることができる場合はまったく作成しないでください。デフォルトで生成された関数が正しいことを実行できるようにします。(逆に言えば、これらのいずれかを作成する必要がある場合は、おそらくすべてを作成する必要があります。)

したがって、問題は「移動セマンティクスを利用できる単一責任コンポーネント クラスはどれか」ということになります。一般的な答えは、リソースを管理するものです。要点は、ムーブ コンストラクター/アサイナーがリソースを新しいオブジェクトに再配置し、古いオブジェクトを無効にするだけで、(コストがかかるか不可能であると推定される) 新しい割り当てとリソースのディープ コピーを回避することです。

代表的な例は、動的メモリを管理するもので、移動操作は単純にポインタをコピーし、古いオブジェクトのポインタをゼロに設定します (したがって、古いオブジェクトのデストラクタは何もしません)。単純な例を次に示します。

class MySpace
{
  void * addr;
  std::size_t len;

public:
  explicit MySpace(std::size_t n) : addr(::operator new(n)), len(n) { }

  ~MySpace() { ::operator delete(addr); }

  MySpace(const MySpace & rhs) : addr(::operator new(rhs.len)), len(rhs.len)
  { /* copy memory */ }

  MySpace(MySpace && rhs) : addr(rhs.addr), len(rhs.len)
  { rhs.len = 0; rhs.addr = 0; }

  // ditto for assignment
};

重要なのは、コピー/移動コンストラクターがメンバー変数の完全なコピーを行うことです。これらの変数自体がリソースへのハンドルまたはポインターである場合にのみ、リソースのコピーを回避できます。これは、移動されたオブジェクトがもはや有効とは見なされず、自由に盗むことができるという合意があるためです。盗むものがなければ、移動するメリットはありません。

于 2011-10-25T06:42:36.330 に答える
9

この場合int、移動コンストラクターがないため、役に立ちません。

ただし、代わりに文字列であると便利です。たとえば、次のようになります。

template<unsigned int N>
class Foo {
public:
    // [snip]

    Foo(Foo<N>&& other) {
        // move each element from other._nums to _nums
        std::move(std::begin(other._nums), std::end(other._nums), &_nums[0]);
    }

    // [snip]

private:
    std::string _nums[N];
};

これで、移動が行われる場所で文字列をコピーすることを回避できます。すべてのコピー/移動コンストラクターを完全に省略した場合、準拠する C++11 コンパイラーが同等のコードを生成するかどうかはわかりません。申し訳ありません。

(つまり、std::moveが配列に対して要素単位の移動を行うように特別に定義されているかどうかはわかりません。)

于 2011-10-25T06:54:39.230 に答える
8

あなたが作成したクラス テンプレートの場合、ムーブ コンストラクターを使用するメリットはありません。

メンバー配列が動的に割り当てられた場合、利点があります。ただし、プレーン配列をメンバーとして使用すると、最適化するものは何もなく、値をコピーすることしかできません。それらを移動する方法はありません。

于 2011-10-25T06:42:14.757 に答える
2

通常、クラスがresourceを管理する ときに move-semantic が実装されます。あなたの場合、クラスはリソースを管理しないため、移動するものがないため、移動セマンティックはコピーセマンティックに似ています。

ムーブセマンティックがいつ必要になるかをよりよく理解する_numsには、配列の代わりにポインターを作成することを検討してください。

template<unsigned int N>
class Foo {
public:
    Foo() 
    {
        _nums = new int[N](); //allocate and zeo-initialized
    }
    Foo(const Foo<N>& other) 
    {
        _nums = new int[N];
        for (int i = 0; i < N; ++i) _nums[i] = other._nums[i];
    }

    Foo(Foo<N>&& other) 
    {
         _nums = other._nums; //move the resource
         other._nums=0; //make it null
    }

    Foo<N> operator=(const Foo<N> & other); //implement it!

    virtual ~Foo() { delete [] _nums; }

private:
    int *_nums;
};
于 2011-10-25T06:47:46.087 に答える