항목 20 : '값에 의한 전달'보다는 '상수객체 참조자에 의한 전달' 방식을 택하는 편이 대개 낫다
기본적으로 C++는 함수로부터 객체를 전달받거나 함수에 객체를 전달할 때 '값에 의한 전달' 방식을 사용합니다. 특별히 다른 방식을 지정하지 않는 한, 함수 매개변수는 실제 인자의 '사본'을 통해 초기화되며, 어떤 함수를 호출한 쪽은 그 함수가 반환한 값의 '사본'을 돌려받습니다. 이들 사본을 만들어내는게 복사 생성자 입니다. 이 점 때문에 '값에 의한 전달'이 고비용의 연산이 되기도 합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
class Person {
public :
Person();
virtual ~Person();
...
private :
std::string name;
std::string address;
};
class Student : public Person {
public :
Student();
~Student();
...
private :
std::string schoolName;
std::string schoolAddress;
};
|
이제 아래의 코드를 봐 주세요. validateStudent라는 함수를 호출하고 있는데, 이 함수는 Student 인자를 전달받고 이 인자가 유효화됐는가를 알려 주는 값을 반환합니다.
1
2
3
4
5
|
bool validateStudent (Student s); // Student를 값으로 전달받는 함수
Student plato;
bool platoIsOk = validateStudent (plato); // 이제 함수를 호출합니다.
|
위 코드는 Student 객체를 값으로 전달하는데 비용으로 생성자 여섯 번에 소멸자를 여섯 번 사용했습니다.
Student 복사 생성자 호출 한 번, Person 복사 생성자 호출 한 번, String 복사 생성자 호출이 네 번 입니다.
이러한 낭비를 줄일 수 있는 방법이 있습니다. 상수객체에 대한 참조자로 전달하게 만드는 것입니다.
1
|
bool validateStudent (const Student& s);
|
이렇게 하면 새로 만들어지는 객체 같은 것이 없기 때문에, 생성자와 소멸자가 전혀 호출되지않는 효율적인 코드로 변합니다.
참조에 의한 전달 방식으로 매개변수를 넘기면 복사손실 문제가 없어지는 장점도 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
|
class Window {
public :
...
std::string name() const;
virtual void display() const;
};
class WindowWithScrollBars : public Window {
public :
...
virtual void display() const;
};
Colored by Color Scripter
|
이제 이것을 가지고 어떤 윈도우의 이름을 출력하고 그 윈도우를 화면에 표시하는 함수를 하나 만들어 보겠습니다. 우선 틀리게 구현한 버전을 가지고 이야기를 시작하죠.
1
2
3
4
5
|
void printNameAndDisplay(Window w) // 매개변수가 복사손실에 당하고 맙니다!
{
std::cout << w.name();
w.display();
}
Colored by Color Scripter
|
복사손실 문제에서 도망가려면, w를 상수객체에 대한 참조자로 전달하도록 만들면 됩니다.
1
2
3
4
5
|
void printNameAndDisplay(const Window& w) // 이제 매개변수는 잘리지 않습니다.
{
std::cout << w.name();
w.display();
}
Colored by Color Scripter
|
C++ 컴파일러의 동작 원리에 관심 있는 분이라면, 참조자는 보통 포인터를 써서 구현된다는 사실을 알고 계시거나 알아내실 겁니다. 즉, 참조자를 전달한다는 것은 결국 포인터를 전달한다는 것과 일맥상통한다는 이야기죠. 이렇게 따져 보면, 전달하는 객체의 타입이 기본제공 타입일 경우에는 참조자로 넘기는 것보다 값으로 넘기는 편이 더 효율적일 때가 많습니다. 이 점은 STL의 반복자와 함수 객체에도 마찬가지입니다.
결론적으로, '값에 의한 전달'이 저비용이라고 가정해도 괜찮은 타입은 기본제공 타입, STL 반복자, 함수 객체 타입, 이렇게 세 가지 뿐입니다. 이 외의 타입에 대해서는 이번 항목에 나온 이야기를 따르시는 것이 좋습니다.
꼭 잊지 말아야 할 것!
1. '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달'을 선호합시다. 대체적으로 효울적일 뿐만 아니라 복사손실 문제까지 막아 줍니다.
2. 이번 항목에서 다룬 법칙은 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에는 맞지 않습니다. 이들에 대해서는 '값에 의한 전달'이 더 적절합니다.
'언어 > C++' 카테고리의 다른 글
[Effective C++] 데이터 멤버가 선언될 곳은 private 영역임을 명심하자 (0) | 2020.01.22 |
---|---|
[Effective C++] 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자 (0) | 2020.01.21 |
[Effective C++] 클래스 설계는 타입 설계와 똑같이 취급하자 (0) | 2020.01.19 |
[Effective C++] 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 (0) | 2020.01.18 |
[Effective C++] new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자 (0) | 2020.01.17 |