이동 의미론 대 const 참조 [중복]

Aug 18 2020

내 클래스에는 문자열 변수가 있으며 생성자에 전달 된 값으로 초기화하고 싶습니다.

선생님은 문자열을 상수 참조로 전달한다고 생각했습니다.

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 초가 생성됩니다 .

  • 멤버는 복사본이 생성됩니다.
  • A std::stringstd::string변환 생성자에 의해 생성되고 멤버는 복사본이 생성됩니다.

# 2- std::stringby 값을 취하는 단일 변환 생성자 .

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

이렇게하면 std::string다음 방법 중 하나로 1 또는 2 초가 생성됩니다 .

  • 인수는 임시 ( + )의 반환 값 최적화에 의해 생성 된 다음 멤버가 이동 생성됩니다.str1str2
  • 인수는 복사가 생성되고 멤버는 이동이 생성됩니다.
  • 인수는 이동 구성이고 멤버는 이동 구성입니다.
  • 인수는 std::string변환 생성자에 의해 생성되고 멤버는 이동 생성됩니다.

# 3-두 개의 변환 생성자를 결합합니다.

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

이렇게하면 std::string다음 방법 중 하나로 1 또는 2 초가 생성됩니다 .

  • 멤버는 복사본이 생성됩니다.
  • 구성원은 이동 구성입니다.
  • A std::stringstd::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))
{}
1 Jose Aug 18 2020 at 18:10

참조를 복사하면 원래 변수의 복사본이 만들어지고 (원래 변수와 새 변수가 서로 다른 영역에 있음) 로컬 변수를 이동하면 로컬 변수가 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더 좋습니다 (이러한 상황에서).

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 경우가 있습니다 좌변 또는 를 rvalue 의는 std::string.

std::string const&버전, 좌변 의 경우는 효율이 충분히있다 참조에 의해 전달하고 복사 . 그러나 rvalue이동 하는 대신 복사 되므로 효율성이 훨씬 낮습니다.

std::string버전, 좌변이 되어 전달 될 때 복사 한 다음 이동 회원에게. 이 경우 rvalue두 번 이동 합니다. 그러나 일반적으로 이동 생성자는 저렴 합니다.

게다가 std::string&&버전에서는 lvalue를 받을 수 없지만 rvalue참조전달 된 다음 이동됩니다. 두 번 이동하는 것보다 낫습니다.

그래서 분명히 STL이 항상하는 것처럼 const&및 둘 다에 대한 모범 사례입니다 &&. 그러나 이동 생성자가 충분히 저렴하다면 값을 전달하고 이동하는 것도 허용됩니다.