1

同じ参照が基本クラスに 2 回転送され、そこでタプルを構築するために使用される次のコードを考えてみましょう。

template<typename ... Ts>
struct Base
{
    template<typename ... _Ts>
    Base(_Ts&& ... ts) : tup{std::forward<_Ts>(ts) ...} {}

    std::tuple <Ts ...> tup;
};

template<typename T>
struct Derived : public Base<T, T>
{
    template<typename _T>
    Derived(_T&& t) : Base<T, T>{t, std::forward<_T>(t)} {}
};

Derived最初にasで基本クラス コンストラクターを呼び出しBase<T, T>{t, std::forward<_T>(t)}、その後でタプル コンストラクターを使用することtup{std::forward<Ts>(ts)...}には、次の理由があります。

が右辺値参照の場合t、最初のタプル引数は に左辺値参照を渡されるtため、 のコピーを介して構築されますがt、2 番目のタプル要素は右辺値参照を取得する必要があるため、可能であれば構築に move を使用します。

このアプローチは、ブレース初期化リストがその引数の左から右への評価を実行すると述べているSO に関するいくつかの質問と回答 (たとえば、 herehere、およびhere ) によってサポートされているようです。

ただし、単純な例で上記のコードを使用すると、実際の動作は (一貫して) 予想とは逆になります。

struct A
{
    A() = default;
    A(A const& other) : vec(other.vec) { std::cout<<"copy"<<std::endl; }
    A(A && other) : vec(std::move(other.vec)) { std::cout<<"move"<<std::endl; }

    std::vector<int> vec = std::vector<int>(100);
};

int main()
{
    Derived<A> d(A{});  //prints first "move", then "copy"

    std::cout<<std::get<0>(d.tup).vec.size()<<std::endl;  //prints 0
    std::cout<<std::get<1>(d.tup).vec.size()<<std::endl;  //prints 100
}

Coliruで gcc を使用した例を次に示します。(gcc コンパイラには、これに関連して 1 回バグがあったようですが、それから約 2 年が経過しており、もはや問題にはなっていないはずです。)

質問:

  • ここでの実装または仮定のどこが間違っていますか?
  • 上記のコードを修正して、期待どおりに動作させるにはどうすればよいでしょうか: 最初にコピーしてから移動しますか?
4

2 に答える 2

2

明示的にコピーを行う必要があります。それ以外の場合は、後でコピーによって使用される参照を渡します (ただし、エイリアスを使用すると、移動されている可能性があります)。

template<typename U>
struct Derived : public Base<U, U>
{
    template<typename T>
    Derived(T&& t) : Base<U, U>{T(t), std::forward<T>(t)} {}
};

デモ

于 2016-02-11T18:44:06.147 に答える
1

オブジェクトの初期化における操作の順序が重要かどうかはわかりません。std::tuple完全転送のため、コンストラクターが呼び出されるまで、コピーや移動は実際には行われません (つまり、左辺値参照と右辺値参照のみが渡されます) 。そして、その時点で、それは の実装の詳細に依存しstd::tupleます。

代わりに次の構造体std::tupleを使用したかどうかを検討してください。my_tup

template<typename T1, typename T2>
struct my_tup
{
    template <typename A, typename B>
    my_tup(A&& a, B&& b) 
        : t1(std::forward<A>(a)), t2(std::forward<B>(b))
    {
    }

    T1 t1;
    T2 t2;
};

これは、予想どおり、「コピー」してから「移動」( coliru ) を出力します。しかし、代わりに次の場合:

template<typename T1, typename T2>
struct my_tup
{
    template <typename A, typename B>
    my_tup(A&& a, B&& b) 
        : t2(std::forward<B>(b)), t1(std::forward<A>(a))
    {
    }

    T2 t2;
    T1 t1;
};

次に、これは「move」、次に「copy」を出力std::tupleします(coliru)。

おそらく、可変個引数テンプレートが展開される方法が原因std::tupleで、引数を右から左に処理する必要があります。これが実装に依存するのか、仕様で指定されているのかはわかりません。

于 2016-02-11T18:39:29.013 に答える