프로그래밍 언어/Effective Modern C++

[Effective Modern C++] 항목 41 ~ 42 : 다듬기

우향우@ 2024. 12. 15. 18:56

항목 41 : 값 전달 함수가 좋은 경우

아래 조건이 만족하면 사용자 정의 클래스 매개변수도 값전달이 유용할 수 있다.

1. 이동이 싸다

2. 함수가 해당 매개변수를 결국 내부에서 복사하는데 사용한다.

3. 복사 가능 타입

 

예시)

class Widget {
public:
  void addName(const std::string& newName)
  { names.push_back(newName); }
  
  void addName(std::string&& newName)
  { names.push_back(std::move(newName); }
  ...
}

class Widget {
public:
  void addName(std::string newName)
  { names.push_back(std::move(newName)); }
  ...
}

여기서 값 전달의 이 점은 함수가 하나라는 점이다. 비용의 차이는 다음과 같다.

LValue

레퍼런스 : 복사 생성 1  

값 : 복사 생성 1, 이동 생성 1

RValue

레퍼런스 : 이동 생성 1

값 : 이동 생성 2

 

값이 이동 생성 하나만 더 비싸기에 이동이 싸다면 나쁘지 않은 선택이다.

 

하지만 만약 내부에서 복사 생성이 아닌 대입을 하는 경우라면 값 전달 방식은 레퍼런스 방식에 비해 생성이 추가되므로 클래스의 생성 비용도 고려해야한다. 또한 부모 타입 매개변수에 자식 타입을 넣으면 값 전달은 잘림 문제가 있으므로 이 부분도 고려해야한다.

 

항목 41 : push_back  vs  emplace_back

std::vector<std::string> vs;
vs.push_back("abc"); // 1
vs.emplace_back("abc"); // 2

//s는 이미 있던 std::string

vs.push_back(s); // 3
vs.emplace_back(s); // 4

 

push_back은 T의 레퍼런스 타입을 받아 복사 대입을 한다.

emplace_back은 인자를 그대로 전달해 생성을 한다.

위 코드에서 3과 4의 성능은 같지만 1은 2에 비해 임시 객체가 하나 생성되기에 성능이 좋지 않다.

따라서 이런 경우 emplace_back을 쓰는게 좋다.

하지만 경우에 따라 push_back이 좋을 수 있다.

다음의 경우에는 높은 확률로 emplace_back이 성능이 좋다.

 

1. 추가할 값이 컨테이너에 대입이 아닌 생성될 때

push_back은 대입 시 복사 대입을 다이렉트로 하지만 emplace_back은 임시 객체 생성 후 이동 대입을 수행할 수 있기에 push_back이 더 효율적일 수 있다. (책에 정확히 안적혀 있어서 추측한거라 아닐 수도 있음...)

 

2. 인수 형식과 컨테이너 원소 타입이 다를때

위 코드의 경우에 해당함.

 

3. 컨테이너가 중복 방지 등에 의해 새 값을 거부할일이 없을때

중복 방지등이 필요한 컨테이너는 비교등을 위해 임시 객체가 필요한데 이때 emplace쪽이 더 성능이 안 좋을 수 있다.

 

 

추가로 성능과 별개로 아래 같은 경우에는 push_bakc이 좋다.

ptrs.push_back(std::shared_ptr<Widget>(new Widget, killWidget));

ptrs.emplace_back(new Widget, killWidget));

아래는 emplace_back 실행 중 예외 발생 시 누수가 발생한다.

사실 shared_ptr 생성 문장을 따로 분리하는게 가장 좋은데 이 경우에도 양쪽 성능이 같기 때문에 그냥 push_back쓰자.

 

마지막으로 emplace_back은 생성자 인자를 그대로 넘겨받는 것이기에 push_back과 달리 explicit 생성자로도 생성이 될 수 있다. 조심하자.