如何解决移动语义与const参考
我的班级有字符串变量,我想用传递给构造函数的值来初始化它们。
我的老师认为我们可以将字符串作为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) {}
这将通过以下一种方式创建1或2个std::string
:
- 成员是复制构造的。
-
std::string
由std::string
转换构造函数构造,然后成员被复制构造。
#2-单个转换构造函数按值获取std::string
。
MyClass(std::string title) : title(std::move(title)) {}
这将通过以下一种方式创建1或2个std::string
:
- 由return value optimization从临时(
str1
+str2
构造参数,然后成员被移动构造。 - 参数是复制构造的,然后成员是移动构造的。
- 参数是移动构造的,然后成员是移动构造的。
- 该参数由
std::string
转换构造函数构造,然后将该成员移动构造。
#3-组合两个转换构造函数。
MyClass(const std::string& title) : title(title) {}
MyClass(std::string&& title) : title(std::move(title)) {}
这将通过以下一种方式创建1或2个std::string
:
- 成员是复制构造的。
- 该成员是移动构造的。
-
std::string
由std::string
转换构造函数构造,然后成员被移动构造。
到目前为止,选项#3
似乎是最有效的选项。让我们进一步检查一些选项。
#4-与#3类似,但将移动转换构造函数替换为转发构造函数。
MyClass(const std::string& title) : title(title) {} // A
template<typename... Args>
explicit MyClass(Args&&... args) : title(std::forward<Args>(args)...) {} // B
这将始终以下列方式之一创建1 std::string
:
- 该成员是通过
A
构建的副本。 - 该成员是通过
B
构建的。 - 该成员由
std::string
(可能是转换的)构造函数通过B
构造。
#5-仅用于转发构造函数-从#4中删除复制转换构造函数。
template<typename... Args>
explicit MyClass(Args&&... args) : title(std::forward<Args>(args)...) {}
这将总是像#4一样创建1 std::string
,但是所有操作都是通过转发构造函数完成的。
- 成员是复制构造的。
- 该成员是移动构造的。
- 该成员由
std::string
(可能是转换的)构造函数构造。
#6-单个参数转发转换构造函数。
template<typename T>
explicit MyClass(T&& title) : title(std::forward<T>(title)) {}
这将始终像1在#4和#5中那样创建1 std::string
,但只会采用一个参数并将其转发给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::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::allocator<char> >&): # @MyClass(std::__cxx11::basic_string<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::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],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是您在课程中的成员字符串。
您可以在这里找到有用的帮助:Constructor member initializer lists
,有2种情况:std::string
的 lvalue 或 rvalue 。
在std::string const&
版本中,左值情况足够有效,通过引用传递然后复制。但是右值将被复制,而不是移动,这会大大降低效率。
在std::string
版本中,左值在传递时被复制,然后移动到该成员。在这种情况下,右值将被移动两次。但通常是移动构造函数便宜。
此外,在std::string&&
版本中,它不能接收左值,但是右值已通过引用被传递然后被移动 ,胜过两次移动。
很明显,这是const&
和&&
的最佳实践,就像STL总是那样。但是如果move构造函数足够便宜,那么按值传递和move也是可以接受的。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。