pass-by-value 및 std :: move의 장점


106

나는 지금 C ++를 배우고 있고 나쁜 습관을 들이지 않으려 고 노력한다. 내가 이해하는 바에 따르면, clang-tidy에는 많은 "모범 사례"가 포함되어 있으며 가능한 한 최선을 다하려고 노력합니다 ( 아직 좋은 것으로 간주되는 이유 를 반드시 이해하지는 못하더라도 ). 여기에서 권장되는 사항을 이해하십시오.

튜토리얼에서이 클래스를 사용했습니다.

class Creature
{
private:
    std::string m_name;

public:
    Creature(const std::string &name)
            :  m_name{name}
    {
    }
};

이것은 clang-tidy에서 참조 대신 값을 전달하고을 사용해야한다는 제안으로 이어집니다 std::move. 내가 할 경우, 나는 제안 할 수 name및 경고 (이 때마다 복사되지 않습니다 보장하기 위해)에 대한 참조를 std::move하기 때문에 영향을주지 않습니다 nameA는 const내가 그것을 제거해야합니다 있도록합니다.

경고가 표시되지 않는 유일한 방법은 const모두 제거하는 것 입니다.

Creature(std::string name)
        :  m_name{std::move(name)}
{
}

논리적으로 보이는 유일한 이점은 const 원래 문자열을 엉망으로 만드는 것을 방지하는 것이므로 (값으로 전달했기 때문에 발생하지 않음). 하지만 CPlusPlus.com 에서 읽었습니다 .

-표준 라이브러리에서 이동은 이동 된 개체가 유효하지만 지정되지 않은 상태로 남아 있음을 의미합니다. 즉, 이러한 작업 후에 이동 된 개체의 값은 삭제되거나 새 값이 할당되어야합니다. 그렇지 않으면 액세스하면 지정되지 않은 값이 생성됩니다.

이제 다음 코드를 상상해보십시오.

std::string nameString("Alex");
Creature c(nameString);

nameString값으로 전달 되기 때문에 생성자 내부 std::move에서만 무효화 name되고 원래 문자열을 건드리지 않습니다. 그러나 이것의 장점은 무엇입니까? 아무튼 내용이 한 번만 복사되는 것 같습니다. 전화 할 때 참조로 전달하면m_name{name} 전달하면 전달하면 값으로 전달되고 이동하면. 나는 이것이 가치를 전달하고 사용하지 않는 것보다 낫다는 것을 이해합니다 std::move(두 번 복사되기 때문에).

두 가지 질문이 있습니다.

  1. 여기서 무슨 일이 일어나고 있는지 올바르게 이해 했습니까?
  2. std::move참조로 전달하고 호출하는 것의 장점이 m_name{name}있습니까?

3
참조로 패스로, Creature c("John");추가 사본합니다
user253751

1
이 링크 는 귀중한 읽기가 될 수 있으며 전달 std::string_view및 SSO도 다룹니다 .
lubgr

나는 clang-tidy가독성을 희생하면서 불필요한 마이크로 최적화에 집착 할 수있는 좋은 방법임을 발견했습니다 . 여기서 물어볼 질문은 무엇보다 먼저 생성자를 실제로 몇 번 호출 하는가 Creature입니다.
cz

답변:


37
  1. 여기서 무슨 일이 일어나고 있는지 올바르게 이해 했습니까?

예.

  1. std::move참조로 전달하고 호출하는 것의 장점이 m_name{name}있습니까?

추가 오버로드없이 쉽게 파악할 수있는 기능 서명. 서명은 인수가 복사 될 것임을 즉시 보여줍니다. 이렇게하면 호출자가const std::string& 참조가 데이터 멤버로 저장되어 나중에 매달려 참조가 될 수 않아도됩니다. 그리고 rvalue가 함수에 전달 될 때 불필요한 복사를 피하기 위해 std::string&& nameconst std::string&인수 를 오버로드 할 필요가 없습니다 . lvalue 전달

std::string nameString("Alex");
Creature c(nameString);

인수를 값으로받는 함수에 대해 하나의 복사본과 하나의 이동 구성이 발생합니다. 동일한 함수에 rvalue 전달

std::string nameString("Alex");
Creature c(std::move(nameString));

두 개의 이동 구성이 발생합니다. 반대로 함수 매개 변수가const std::string& 이면 rvalue 인수를 전달하더라도 항상 복사본이 있습니다. 이것은 인수 유형이 이동 생성에 저렴한 한 이점입니다 (이는std::string ).

그러나 고려해야 할 단점이 있습니다. 함수 인수를 다른 변수에 할당하는 함수에는 추론이 작동하지 않습니다 (초기화하는 대신).

void setName(std::string name)
{
    m_name = std::move(name);
}

m_name재 할당되기 전에 참조 하는 리소스의 할당이 해제됩니다 . 나는 Effective Modern C ++의 Item 41 과이 질문을 읽는 것이 좋습니다 .


특히 이것은 선언문을보다 직관적으로 읽을 수 있도록합니다. 귀하의 답변의 할당 해제 부분을 완전히 파악하고 링크 된 스레드를 이해하고 있는지 확실하지 않으므로 사용 move하는 경우 공간이 할당 해제됩니다. 을 사용하지 않으면 move할당 된 공간이 너무 작아서 새 문자열을 저장할 수없는 경우 에만 할당이 해제되어 성능이 향상됩니다. 그 맞습니까?
Blackbot

1
네, 바로 그 거에요. 매개 변수 m_name에서 할당 할 때 const std::string&내부 메모리는 m_name적합 할 때 까지 재사용 됩니다. 이동 할당시 m_name메모리는 미리 할당 해제되어야합니다. 그렇지 않으면 할당의 오른쪽에서 자원을 "훔치는"것이 불가능했습니다.
lubgr

언제 댕글 링 레퍼런스가 되나요? 초기화 목록이 딥 카피를 사용한다고 생각합니다.
Li Taiji

104
/* (0) */ 
Creature(const std::string &name) : m_name{name} { }
  • 전달 된 lvalue는에 바인딩 된 name다음에 복사 됩니다 m_name.

  • 전달 된 rvalue는에 바인딩 된 name다음에 복사 됩니다 m_name.


/* (1) */ 
Creature(std::string name) : m_name{std::move(name)} { }
  • 전달 된 lvalue 는로 복사name다음로 이동 됩니다 m_name.

  • 전달 된 를 rvalue가 되어 이동 으로 name다음되고, 이동m_name.


/* (2) */ 
Creature(const std::string &name) : m_name{name} { }
Creature(std::string &&rname) : m_name{std::move(rname)} { }
  • 전달 된 lvalue는에 바인딩 된 name다음에 복사 됩니다 m_name.

  • 전달 된 rvalue는에 바인딩 된 rname다음로 이동 됩니다 m_name.


이동 작업은 일반적으로 복사본보다 빠르기 때문에 임시를 많이 통과하면 (1)(0) 보다 낫습니다 . (2) 복사 / 이동 측면에서 최적이지만 코드 반복이 필요합니다.

완벽한 포워딩 으로 코드 반복을 피할 수 있습니다 .

/* (3) */
template <typename T,
          std::enable_if_t<
              std::is_convertible_v<std::remove_cvref_t<T>, std::string>, 
          int> = 0
         >
Creature(T&& name) : m_name{std::forward<T>(name)} { }

T이 생성자가 인스턴스화 할 수있는 유형의 도메인을 제한하기 위해 선택적 으로 제한 할 수 있습니다 (위에 표시된대로). C ++ 20은이를 개념 으로 단순화하는 것을 목표로합니다 .


C ++ 17에서 prvalues보장 된 복사 제거의 영향을받으며 , 적용 가능한 경우 함수에 인수를 전달할 때 복사 / 이동 수를 줄입니다.


(1)의 경우 pr-value 및 xvalue 대소 문자는 C ++ 17 이후 동일하지 않습니다.
Oliv

1
이 경우 완벽한 포워드를 위해 SFINAE가 필요 하지 않습니다 . 명확하게하기 위해서만 필요합니다. 그것은이다 그럴듯 나쁜 인수를 전달할 때 잠재적 인 오류 메시지에 대한 도움
Caleth

@Oliv 예. xvalue는 이동해야하며 prvalue는 생략 할 수 있습니다. :)
Rakete1111

1
다음 Creature(const std::string &name) : m_name{std::move(name)} { }과 같이 쓸 수 있습니까 ? (2) ?
skytree

4
@skytree : 이동하면 소스가 변경되므로 const 개체에서 이동할 수 없습니다. 그것은 컴파일되지만 복사본을 만들 것입니다.
Vittorio Romeo

1

어떻게 당신이 전달하는 유일한 변수는 여기 아니라, 무엇을 당신이 통과하면 둘 사이에 큰 차이가 있습니다.

C ++에는 모든 종류의 값 범주 가 있으며이 "관용구"는 rvalue (예 : "Alex-string-literal-that-constructs-temporary-std::string"또는 std::move(nameString)) 를 전달 하여 0 개의 복사본std::string 이 만들어지는 경우 에 존재합니다 (형식이 복사 구성 가능일 필요도 없음). rvalue 인수의 경우), std::string의 이동 생성자 만 사용합니다 .

다소 Q & A 관련 .


1

pass-by- (rv) reference에 대한 pass-by-value-and-move 접근 방식에는 몇 가지 단점이 있습니다.

  • 2 개 대신 3 개의 개체가 생성됩니다.
  • 객체를 값으로 전달하면 추가 스택 오버 헤드가 발생할 수 있습니다. 일반 문자열 클래스도 일반적으로 포인터보다 3 ~ 4 배 이상 크기 때문입니다.
  • 인자 객체 생성은 호출자 측에서 수행되어 코드가 부풀어 오른다.

3 개의 개체가 생성되는 이유를 설명해 주시겠습니까? 내가 이해하는 바에 따르면 "Peter"를 문자열로 전달할 수 있습니다. 이것은 생성되고, 복사되고, 이동 될 것입니다. 그렇지 않습니까? 그리고 어느 시점에서 스택이 사용되지 않을까요? 생성자 호출 시점이 아니라 m_name{name}복사 되는 부분에서?
Blackbot

@Blackbot 나는 당신의 예를 언급 std::string nameString("Alex"); Creature c(nameString);한 객체 하나는 nameString이고 다른 하나는 함수 인수이며 세 번째는 클래스 필드입니다.
user7860670

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.