8

次のクラスを検討してください。

#include <iostream>
#include <string>

class A
{
    std::string test;
public:
    A (std::string t) : test(std::move(t)) {}
    A (const A & other) { *this = other; }
    A (A && other) { *this = std::move(other); }

    A & operator = (const A & other)
    {
        std::cerr<<"copying A"<<std::endl;
        test = other.test;
        return *this;
    }

    A & operator = (A && other)
    {
        std::cerr<<"move A"<<std::endl;
        test = other.test;
        return *this;
    }
};

class B
{
    A a;
public:   
    B (A && a) : a(std::move(a)) {}
    B (A const & a) : a(a) {}
};

を作成するとき、右辺値の場合は 1 つの移動、左辺値の場合は 1 つのコピーBの最適な転送パスが常にあります。A

1 つのコンストラクターで同じ結果を達成することは可能ですか? この場合は大きな問題ではありませんが、複数のパラメーターはどうでしょうか。パラメータリスト内の左辺値と右辺値のすべての可能な出現の組み合わせが必要になります。

これはコンストラクターに限らず、関数パラメーター (セッターなど) にも当てはまります。

注: この質問は厳密にはclass B; class Aコピー/移動呼び出しがどのように実行されるかを視覚化するためだけに存在します。

4

3 に答える 3

9

「値による」アプローチはオプションです。それはあなたが持っているものほど最適ではありませんが、1つのオーバーロードだけを必要とします:

class B
{
    A a;
public:   
    B (A _a) : a(move(_a)) {}
};

コストは、左辺値とx値の両方に対して1ムーブの追加建設ですが、これはprvalues(1ムーブ)には依然として最適です。「xvalue」は、std::moveを使用して右辺値にキャストされた左辺値です。

「完璧な転送」ソリューションを試すこともできます。

class B
{
    A a;
public:   
    template <class T,
              class = typename std::enable_if
              <
                 std::is_constructible<A, T>::value
              >::type>
    B (T&& _a) : a(std::forward<T>(_a)) {}
};

これにより、最適な数のコピー/移動構造に戻ることができます。ただし、テンプレートコンストラクターは、過度に一般的でないように制約する必要があります。上で行ったように、is_constructibleの代わりにis_convertibleを使用することをお勧めします。これも単一のコンストラクターソリューションですが、パラメーターを追加すると、制約がますます複雑になります。

:上記の制約が必要な理由は、これがないと、のクライアントがBクエリを実行したときに間違った答えを取得するためstd::is_constructible<B, their_type>::valueです。に適切な制約がないと、誤ってtrueと答えますB

これらのソリューションのどれもが常に他のソリューションより優れているわけではないと思います。ここで行うべきエンジニアリングのトレードオフがあります。

于 2012-05-06T16:45:08.200 に答える
2

のコンストラクターに推定パラメーター型を使用しますB

template <typename T> explicit B(T && x) : a(std::forward<T>(x) { }

これは、Aオブジェクトが構築可能な任意の引数に対して機能します。

Aさまざまな数の引数を持つ複数のコンストラクターがある場合は、...どこにでも追加することで、全体を可変個にすることができます。

ただし、@Howard が言うように、実際にはそうでない引数からクラスが構築可能に見えないように、制約を追加する必要があります。

于 2012-05-06T16:38:00.030 に答える
1

stringサンプルのがである場合std::string、単に気にしないでください。デフォルトで提供されるコピーと移動は、それぞれのメンバーを呼び出します。またstd::string、コピーと移動の両方が実装されているため、一時変数が移動され、変数がコピーされます。

特定のコピーを定義してctorを移動し、割り当てる必要はありません。コンストラクターをそのままにしておくことができます

A::A(string s) :test(std::move(s)) {}

一般に、コピーと移動の簡単な実装は次のようになります。

class A
{
public:
    A() :p() {}

    A(const A& a) :p(new data(*a.p)) {} //copy
    A(A&& a) :p(a.p) { a.p=0; }         //move

    A& operator=(A a) //note: pass by value
    { clear(); swap(a); return *this; }
    ~A() { clear(); }

    void swap(A& a) { std::swap(p,a.p); }
    void clear() { delete p; p=0; }

private:

    data* p;
};

operator=、内部で移動される値を取ります。一時的なものである場合は移動され、変数である場合はコピーされます。コピーと移動の違いには、別個のコンストラクターが必要ですが、Aを次のように導出する場合

class B: public A
{
...
};

Bのデフォルトのcopy-ctorはAのコピーを呼び出し、Bのデフォルトの移動はAの移動を呼び出し、Bのすべてのデフォルトの割り当て演算子はAに対して定義された唯一の演算子を呼び出すため、何もオーバーライドする必要はありません。転送されたものに応じて移動またはコピーします)。

于 2012-05-06T16:54:03.733 に答える