이동 의미론 대 const 참조 [중복]
내 클래스에는 문자열 변수가 있으며 생성자에 전달 된 값으로 초기화하고 싶습니다.
선생님은 문자열을 상수 참조로 전달한다고 생각했습니다.
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
by 값을 취하는 단일 변환 생성자 .
MyClass(std::string title) : title(std::move(title)) {}
이렇게하면 std::string
다음 방법 중 하나로 1 또는 2 초가 생성됩니다 .
- 인수는 임시 ( + )의 반환 값 최적화에 의해 생성 된 다음 멤버가 이동 생성됩니다.
str1
str2
- 인수는 복사가 생성되고 멤버는 이동이 생성됩니다.
- 인수는 이동 구성이고 멤버는 이동 구성입니다.
- 인수는
std::string
변환 생성자에 의해 생성되고 멤버는 이동 생성됩니다.
# 3-두 개의 변환 생성자를 결합합니다.
MyClass(const std::string& title) : title(title) {}
MyClass(std::string&& title) : title(std::move(title)) {}
이렇게하면 std::string
다음 방법 중 하나로 1 또는 2 초가 생성됩니다 .
- 멤버는 복사본이 생성됩니다.
- 구성원은 이동 구성입니다.
- A
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
이렇게하면 항상 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을 생성 하지만 하나의 인수 만 가져와 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))
{}
참조를 복사하면 원래 변수의 복사본이 만들어지고 (원래 변수와 새 변수가 서로 다른 영역에 있음) 로컬 변수를 이동하면 로컬 변수가 rvalue로 캐스트됩니다 (원래 변수와 새 변수가 서로 다른 영역에 있음).
컴파일러 관점에서 볼 때 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 경우가 있습니다 좌변 또는 를 rvalue 의는 std::string
.
에 std::string const&
버전, 좌변 의 경우는 효율이 충분히있다 참조에 의해 전달하고 복사 . 그러나 rvalue 는 이동 하는 대신 복사 되므로 효율성이 훨씬 낮습니다.
에 std::string
버전, 좌변이 되어 전달 될 때 복사 한 다음 이동 회원에게. 이 경우 rvalue 는 두 번 이동 합니다. 그러나 일반적으로 이동 생성자는 저렴 합니다.
게다가 std::string&&
버전에서는 lvalue를 받을 수 없지만 rvalue 는 참조 로 전달 된 다음 이동됩니다. 두 번 이동하는 것보다 낫습니다.
그래서 분명히 STL이 항상하는 것처럼 const&
및 둘 다에 대한 모범 사례입니다 &&
. 그러나 이동 생성자가 충분히 저렴하다면 값을 전달하고 이동하는 것도 허용됩니다.