移動セマンティクスと定数参照[重複]

Aug 18 2020

私のクラスには文字列変数があり、コンストラクターに渡された値でそれらを初期化したいです。

私の先生は、文字列をconst-referenceとして渡すと考えました。

MyClass::MyClass(const std::string &title){
  this->title = title
}

ただし、Clang-Tidyはmoveコマンドの使用を提案しています。

MyClass::MyClass(std::string title){
  this->title = std::move(title)
}

だから私は現代のC ++でこれを行う正しい方法は何であるか疑問に思っています。

私はすでに周りを見回しましたが、私の質問に本当に答えるものは何もありませんでした。前もって感謝します!

回答

3 TedLyngmo Aug 18 2020 at 19:32

どちらもtitle最初にデフォルトで構成され、次にコピー代入またはムーブ代入であるため、最適なものはありません。メンバー初期化子リストを使用します。

MyClass::MyClass(const std::string& title) : title(title) {}         // #1
// or
MyClass::MyClass(std::string title) : title(std::move(title)) {}     // #2
//or
MyClass::MyClass(const std::string& title) : title(title) {}         // #3
MyClass::MyClass(std::string&& title) : title(std::move(title)) {}   // #3

それらを見て、C ++ 17で何が起こるか見てみましょう。


#1- const&。を取る単一の変換コンストラクター。

MyClass::MyClass(const std::string& title) : title(title) {}

これstd::stringにより、次のいずれかの方法で1つまたは2つのが作成されます。

  • メンバーはコピーで作成されます。
  • Astd::stringstd::string変換コンストラクターによって構築され、メンバーはコピー構築されます。

#2-std::string値による単一の変換コンストラクター。

MyClass(std::string title) : title(std::move(title)) {}

これstd::stringにより、次のいずれかの方法で1つまたは2つのが作成されます。

  • 引数は一時(+ )からの戻り値の最適化によって構築され、メンバーは移動構築されます。str1str2
  • 引数はコピー構築され、次にメンバーは移動構築されます。
  • 引数はmoveconstructedであり、次にメンバーはmoveconstructedです。
  • 引数はstd::string変換コンストラクターによって構築され、メンバーはmove構築されます。

#3-2つの変換コンストラクターを組み合わせる。

MyClass(const std::string& title) : title(title) {}
MyClass(std::string&& title) : title(std::move(title)) {}

これstd::stringにより、次のいずれかの方法で1つまたは2つのが作成されます。

  • メンバーはコピーで作成されます。
  • メンバーは移動構築されます。
  • Astd::stringstd::string変換コンストラクターによって構築され、次にメンバーはmove構築されます。

これまでのところ、オプション#3が最も効率的なオプションのようです。さらにいくつかのオプションを確認しましょう。


#4-#3と同様ですが、移動変換コンストラクターを転送コンストラクターに置き換えます。

MyClass(const std::string& title) : title(title) {}                       // A
template<typename... Args>
explicit MyClass(Args&&... args) : title(std::forward<Args>(args)...) {}  // B

これstd::stringにより、次のいずれかの方法で常に1が作成されます。

  • メンバーは、を介して作成されたコピーAです。
  • メンバーは、を介して構築された移動Bです。
  • メンバーは、std::stringを介して(おそらく変換する)コンストラクターによって構築されBます。

#5-転送コンストラクターのみ-コピー変換コンストラクターを#4から削除します。

template<typename... Args>
explicit MyClass(Args&&... args) : title(std::forward<Args>(args)...) {}

これによりstd::string、#4のように常に1が作成されますが、すべては転送コンストラクターを介して行われます。

  • メンバーはコピーで作成されます。
  • メンバーは移動構築されます。
  • メンバーは、std::string(おそらく変換する)コンストラクターによって構築されます。

#6-単一引数の転送変換コンストラクター。

template<typename T>
explicit MyClass(T&& title) : title(std::forward<T>(title)) {}

これによりstd::string、#4と#5のように常に1が作成されますが、引数を1つだけ取り、std::stringコンストラクターに転送します。

  • メンバーはコピーで作成されます。
  • メンバーは移動構築されます。
  • メンバーは、std::string変換コンストラクターによって構築されます。

コンストラクターで#6複数の引数を取りたい場合は、オプションを簡単に使用して完全な転送を行うことができますMyClassintメンバーと別のstd::stringメンバーがいるとしましょう。

template<typename T, typename U>
MyClass(int X, T&& title, U&& title2) :
    x(X),
    title(std::forward<T>(title)),
    title2(std::forward<U>(title2))
{}
1 Jose Aug 18 2020 at 18:10

参照をコピーすると、元の変数のコピーが作成され(元の変数と新しい変数は異なる領域にあります)、ローカル変数のキャストをローカル変数の右辺値に移動します(また、元の変数と新しい変数は異なる領域にあります)。

コンパイラの観点からは、moveより高速である可能性があります(そしてより高速です)。

#include <string>

void MyClass(std::string title){
  std::string title2 = std::move(title);
}

次のように変換されます。

MyClass(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >): # @MyClass(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >)
        sub     rsp, 40
        mov     rax, rdi
        lea     rcx, [rsp + 24]
        mov     qword ptr [rsp + 8], rcx
        mov     rdi, qword ptr [rdi]
        lea     rdx, [rax + 16]
        cmp     rdi, rdx
        je      .LBB0_1
        mov     qword ptr [rsp + 8], rdi
        mov     rsi, qword ptr [rax + 16]
        mov     qword ptr [rsp + 24], rsi
        jmp     .LBB0_3
.LBB0_1:
        movups  xmm0, xmmword ptr [rdi]
        movups  xmmword ptr [rcx], xmm0
        mov     rdi, rcx
.LBB0_3:
        mov     rsi, qword ptr [rax + 8]
        mov     qword ptr [rsp + 16], rsi
        mov     qword ptr [rax], rdx
        mov     qword ptr [rax + 8], 0
        mov     byte ptr [rax + 16], 0
        cmp     rdi, rcx
        je      .LBB0_5
        call    operator delete(void*)
.LBB0_5:
        add     rsp, 40
        ret

しかしながら、

void MyClass(std::string& title){
  std::string title = title;
}

より大きなコードを生成します(GCCの場合と同様):

MyClass(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&): # @MyClass(std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >&)
        push    r15
        push    r14
        push    rbx
        sub     rsp, 48
        lea     r15, [rsp + 32]
        mov     qword ptr [rsp + 16], r15
        mov     r14, qword ptr [rdi]
        mov     rbx, qword ptr [rdi + 8]
        test    r14, r14
        jne     .LBB0_2
        test    rbx, rbx
        jne     .LBB0_11
.LBB0_2:
        mov     qword ptr [rsp + 8], rbx
        mov     rax, r15
        cmp     rbx, 16
        jb      .LBB0_4
        lea     rdi, [rsp + 16]
        lea     rsi, [rsp + 8]
        xor     edx, edx
        call    std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::_M_create(unsigned long&, unsigned long)
        mov     qword ptr [rsp + 16], rax
        mov     rcx, qword ptr [rsp + 8]
        mov     qword ptr [rsp + 32], rcx
.LBB0_4:
        test    rbx, rbx
        je      .LBB0_8
        cmp     rbx, 1
        jne     .LBB0_7
        mov     cl, byte ptr [r14]
        mov     byte ptr [rax], cl
        jmp     .LBB0_8
.LBB0_7:
        mov     rdi, rax
        mov     rsi, r14
        mov     rdx, rbx
        call    memcpy
.LBB0_8:
        mov     rax, qword ptr [rsp + 8]
        mov     qword ptr [rsp + 24], rax
        mov     rcx, qword ptr [rsp + 16]
        mov     byte ptr [rcx + rax], 0
        mov     rdi, qword ptr [rsp + 16]
        cmp     rdi, r15
        je      .LBB0_10
        call    operator delete(void*)
.LBB0_10:
        add     rsp, 48
        pop     rbx
        pop     r14
        pop     r15
        ret
.LBB0_11:
        mov     edi, offset .L.str
        call    std::__throw_logic_error(char const*)
.L.str:
        .asciz  "basic_string::_M_construct null not valid"

そうです、std::move(このような状況では)より良いです。

abraxas Aug 18 2020 at 18:19

const参照を使用してから、メンバー初期化子リストを使用してもかまいません。

MyClass(const std::string &title) : m_title{title}

ここで、m_titleはクラスのメンバー文字列です。

ここで役立つヘルプを見つけることができます:コンストラクターメンバー初期化子リスト

RedFog Aug 18 2020 at 18:32

:2例がある左辺値または右辺値のはstd::string

std::string const&、バージョン、左辺値の場合は、効率的なことに、ある参照渡した後、コピーされました。ただし、右辺値移動されるのではなくコピーされるため、効率が大幅に低下します。

std::stringバージョン、左辺値がされて渡されたときにコピーされ、その後、移動メンバーに。この場合、右辺値2回移動されます。しかし、一般的には安価です、moveコンストラクター。

さらに、std::string&&バージョンでは、左辺値を受け取ることはできませんが、右辺値参照によって渡されてから移動されます。これは、2回移動するよりも優れています。

明らかに、STLが常に行うように、との両方const&&&使用することをお勧めします。ただし、moveコンストラクターが十分に安価な場合は、値を渡して移動するだけでもかまいません。