일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- moreeffectiveC++
- UE_LOG
- UML관련
- 언리얼엔진구조체
- 정렬알고리즘
- enumasByue
- BFS
- 크리티컬섹션
- C++최적화
- 데이터애셋
- 자료구조
- 강참조
- 약참조
- stl
- 프로그래머스
- C++
- 스마트포인터
- 알고리즘
- 언리얼가비지컬렉터
- 애셋로드
- 람다
- 람다사용정렬
- dataasset
- 델리게이트
- map
- 선택정렬
- unorder_map
- 정렬
- UELOG
- UE4 커스텀로그
- Today
- Total
기억을 위한 기록들
[CPP-effective] 4-3 '값에 의한 전달'보단 '상수객체 참조자에 의한 전달'방식이 대게 낫다. 본문
[CPP-effective] 4-3 '값에 의한 전달'보단 '상수객체 참조자에 의한 전달'방식이 대게 낫다.
에드윈H 2021. 4. 13. 14:22C++에서 함수로부터 객체를 전달받거나, 함수에 객체를 전달할 때 '값에 의한 전달(Pass By Value)' 방식을 사용한다.
실제 인자의 '사본'을 통해 초기화되며, 어떤 함수를 호출한 쪽은 그 함수가 반환한 값의 '사본'을 돌려받는다.
사본을 만들어내는 원천이 바로 복사 생성자이다. 이 점 때문에 '값에 의한 전달'이 고비용의 연산이 된다.
어떤 클래스 A와 A를 상속받는 클래스 B 두 개가 있다고 할 때,
어떤 함수를 호출하려고 한다.
B myB;
bool DoSomething(B ref);
B 클래스를 값으로 전달받는 함수가 있으면,
myB를 매개변수 ref로 초기화시키기 위해 복사 생성자가 호출된다.
게다가 ref는 DoSomething함수가 끝나면서 소멸된다.
이렇게 되면 총 B클래스의 복사생성자 호출 한번 B클래스 소멸자 호출 한 번씩 일어나게 된다.
이렇게만 끝나면 그나마 다행인데 클래스 안에 멤버 변수도 있고 B클래스는 A클래스의 상속까지 받고 있기 때문에, A클래스도 생성된다. 단지 B클래스를 값으로 전달했을 뿐인데, B 복사 생성자 호출, A클래스 복사생성자 호출이 일어나고 소멸자도 각각 한 번씩 일어나면 총 2번의 생성자와 2번의 소멸자가 호출되고 +@로 멤버 변수들에 대한 호출도 일어나게 된다...
이렇게 한 개의 전달을 위해 여러 번의 생성과 호출을 거치는 것보다 좋은 방법이 바로 상수 객체에 대한 참조자(reference-to-const)로 전달하게 하는 것이다.
B myB;
bool DoSomething(const B& ref);
이렇게 되면 훨씬 효율적으로 여러 번의 생성자와 소멸자가 아니게 된다. const로 함수 안에서의 알 수 없는 변화로부터 ref객체를 지켜줄 수 있게 된다. 클래스 B는 참조에 의한 전달 방식이게 되고 const가 붙지 않게되면 함수안에서 ref값이 변경되면 원래의 객체 내용이 어떻게 변할지 모르는 불안감에 휩싸이게 된다
참조에 의한 전달 방식으로 매개변수를 넘기면 복사 손실 문제(slicing problem)가 없어지는 장점도 있다.
C++ 컴파일러에서는 참조자는 보통 포인터를 써서 구현되어있다. 결국엔 참조자를 전달하는 것은 결국 포인터를 전달한다는 것과 같다. 이렇게 보면 기본 제공 타입(int 등) 일 경우에는 참조자로 넘기는 것보다 값으로 넘기는 편이 더 효율적일 때가 많다.
'값에 의한 전달'과 '상수 객체의 참조에 의한 전달 중 하나를 선택해야 할 때, 기본 제공 타입에 대해서는 '값에 의한 전달'을 선택하더라도 이상한 게 아니라는 것이다. 이점은 STL 반복자와 함수 객체에도 마찬가지로, 예전부터 반복자와 함수 객체는 값으로 전달되도록 설계 해왔기 때문이다.
참고로, 반복자와 함수 객체를 구현할 때는 반드시
1. 복사 효율을 높일 것
2. 복사 손실 문제에 노출되지 않도록 만드는 것.
두 개가 필수이다.
그렇다고 해서 단순히 기본 제공 타입의 타입 크기만 작다고 그 객체의 복사 생성자 호출이 저비용이라는 뜻이 아니다.
데이터 멤버가 달랑 포인터 하나뿐인 객체가 꽤 많이 널려 있긴 하지만, 이런 객체를 복사하는 데는 그 포인터 멤버가 가리키는 대상까지 복사하는 작업도 따라다녀야 한다. 크기가 작다고 쉽지는 않다. 비쌀 수도 있다.
사용자 정의 타입 크기는 언제든 변화에 노출되어 있다. 지금은 크기가 작더라도 나중에는 커질 수도 있는 것이다.
정리
* '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달'을 선호하자, 대체적으로 효율적일 뿐만 아니라, 복사 손실 문제까지 막아준다.
* 이번 항목에서 다룬 법칙은 기본 제공 타입 및 STL반복자, 그리고 함수 객체 타입에는 맞지 않는다. 이들에 대해서는 '값에 의한 전달'이 더 적절하다.
'C & CPP > Effective C++' 카테고리의 다른 글
[CPP-effective] 4-5 데이터 멤버가 선언될 곳은 private 영역이라고 생각하자. (0) | 2021.04.15 |
---|---|
[CPP-effective] 4-4 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려 하지말자. (0) | 2021.04.13 |
[CPP-effective] 4-2 클래스 설계는 타입 설계와 똑같이 취급하자. (0) | 2021.04.12 |
[CPP-effective] 4-1 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자. (0) | 2021.04.09 |
[CPP-effective] 3-5 new로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한문장으로 만들자. (0) | 2021.04.08 |