my class has string variables and I want to initialize them with values passed to the constructor.
My teacher thought us to pass strings as a const-reference:
MyClass::MyClass(const std::string &title){
this->title = title
}
However Clang-Tidy suggest using the move command:
MyClass::MyClass(std::string title){
this->title = std::move(title)
}
So I'm wondering what the correct way to do this in modern C++ is.
I already looked around, but nothing really answered my question. Thanks in advance!
Not just a copy; it is also a const copy. So you cannot modify it, invoke any non-const members from it, or pass it as a non-const parameter to any function. If you want a modifiable copy, lose the const decl on protos .
Yes, that is surprising and unconventional. If you want to permit a move, the convention is to have pass by value. You can std::move from inside the function as appropriate, while the caller can std::move into the argument if they decide they want to forfeit ownership.
You are not questioning why const references are allowed to bind to temporaries, but merely why they extend the lifetime of those temporaries. If the lifetime of the temporary returned by bar() were not extended, then any usage of a (exemplified by the line (1)) would lead to undefined behavior.
The important difference is that when passing by const reference, no new object is created. In the function body, the parameter is effectively an alias for the object passed in. Because the reference is a const reference the function body cannot directly change the value of that object.
Implementing move semantics on your own classes is fairly simple. You’ll typically need to define a move constructor, and possibly a move assignment operator. The move constructor just transfers things in, and leaves the source object empty.
To get benefits from move semantics you need some kind of resource (e.g. pointers to dynamically-allocated objects, file descriptors, TCP sockets, I/O streams, running threads, etc.). The source code containing the MemoryBuffer example is here (under MoveSemantics ). You do not need a Github account to download it.
These concepts are common to many languages, and are essential to passing and returning data in your program. As of C++11, you can think of ‘move’ as being a third alternative. It’s expanded the core features of the language, adding move constructors and move-assign operators to your class design arsenal.
There are situations where the compiler will attempt a move implicitly, meaning you don’t have to use std::move (). This can only happen if a non-const movable object is being passed, returned, or assigned by value, and is about to be destroyed automatically.
None is optimal since they both default construct title
first and then copy assign or move assign it. Use the member initializer list.
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
Let's look at them and see what happens in C++17:
#1 - A single converting constructor taking a const&
.
MyClass::MyClass(const std::string& title) : title(title) {}
This will create 1 or 2 std::string
s in one of these ways:
std::string
is constructed by a std::string
converting constructor and then the member is copy constructed.#2 - A single converting constructor taking a std::string
by value.
MyClass(std::string title) : title(std::move(title)) {}
This will create 1 or 2 std::string
s in one of these ways:
str1
+ str2
) and then the member is move constructed.std::string
converting constructor and then the member is move constructed.#3 - Combining two converting constructors.
MyClass(const std::string& title) : title(title) {}
MyClass(std::string&& title) : title(std::move(title)) {}
This will create 1 or 2 std::string
s in one of these ways:
std::string
is constructed by a std::string
converting constructor and then the member is move constructed.So far, option #3
seems to be the most efficient option. Let's check a few options more.
#4 - Like #3 but replacing the moving conversion constructor with a forwarding constructor.
MyClass(const std::string& title) : title(title) {} // A
template<typename... Args>
explicit MyClass(Args&&... args) : title(std::forward<Args>(args)...) {} // B
This will always create 1 std::string
in one of these ways:
A
.B
.std::string
(possibly converting) constructor via B
.#5 - A forwarding constructor only - removing the copying conversion constructor from #4.
template<typename... Args>
explicit MyClass(Args&&... args) : title(std::forward<Args>(args)...) {}
This will always create 1 std::string
like in #4, but all is done via the forwarding constructor.
std::string
(possibly converting) constructor.#6 - A single argument forwarding conversion constructor.
template<typename T>
explicit MyClass(T&& title) : title(std::forward<T>(title)) {}
This will always create 1 std::string
like in #4 and #5 but will only take one argument and forward it to the std::string
constructor.
std::string
converting constructor.Option #6
can easily be used to do perfect forwarding if you want to take multiple arguments in the MyClass
constructor. Let's say you have an int
member and another std::string
member:
template<typename T, typename U>
MyClass(int X, T&& title, U&& title2) :
x(X),
title(std::forward<T>(title)),
title2(std::forward<U>(title2))
{}
Copying a reference creates a copy of the original variable (original and the new one are on different areas), moving a local variable casts to a rvalue your local variable (and again, original and the new one are on different areas).
From the compiler point of view, move
may be (and is) faster:
#include <string>
void MyClass(std::string title){
std::string title2 = std::move(title);
}
translates to:
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
However,
void MyClass(std::string& title){
std::string title = title;
}
generates a bigger code (similar for 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"
So yes, std::move
is better (under these circumstances).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With