移動セマンティクスと定数参照[重複]
私のクラスには文字列変数があり、コンストラクターに渡された値でそれらを初期化したいです。
私の先生は、文字列を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 ++でこれを行う正しい方法は何であるか疑問に思っています。
私はすでに周りを見回しましたが、私の質問に本当に答えるものは何もありませんでした。前もって感謝します!
回答
どちらも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つのが作成されます。
- メンバーはコピーで作成されます。
- A
std::string
はstd::string
変換コンストラクターによって構築され、メンバーはコピー構築されます。
#2-std::string
値による単一の変換コンストラクター。
MyClass(std::string title) : title(std::move(title)) {}
これstd::string
により、次のいずれかの方法で1つまたは2つのが作成されます。
- 引数は一時(+ )からの戻り値の最適化によって構築され、メンバーは移動構築されます。
str1
str2
- 引数はコピー構築され、次にメンバーは移動構築されます。
- 引数は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つのが作成されます。
- メンバーはコピーで作成されます。
- メンバーは移動構築されます。
- A
std::string
はstd::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
複数の引数を取りたい場合は、オプションを簡単に使用して完全な転送を行うことができますMyClass
。int
メンバーと別の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))
{}
参照をコピーすると、元の変数のコピーが作成され(元の変数と新しい変数は異なる領域にあります)、ローカル変数のキャストをローカル変数の右辺値に移動します(また、元の変数と新しい変数は異なる領域にあります)。
コンパイラの観点からは、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
(このような状況では)より良いです。
const参照を使用してから、メンバー初期化子リストを使用してもかまいません。
MyClass(const std::string &title) : m_title{title}
ここで、m_titleはクラスのメンバー文字列です。
ここで役立つヘルプを見つけることができます:コンストラクターメンバー初期化子リスト
:2例がある左辺値または右辺値のはstd::string
。
でstd::string const&
、バージョン、左辺値の場合は、効率的なことに、ある参照渡した後、コピーされました。ただし、右辺値は移動されるのではなくコピーされるため、効率が大幅に低下します。
でstd::string
バージョン、左辺値がされて渡されたときにコピーされ、その後、移動メンバーに。この場合、右辺値は2回移動されます。しかし、一般的には安価です、moveコンストラクター。
さらに、std::string&&
バージョンでは、左辺値を受け取ることはできませんが、右辺値は参照によって渡されてから移動されます。これは、2回移動するよりも優れています。
明らかに、STLが常に行うように、との両方const&
を&&
使用することをお勧めします。ただし、moveコンストラクターが十分に安価な場合は、値を渡して移動するだけでもかまいません。