[Effective C++] 항목20 : '값에 의한 전달'보다는 '상수객체 참조자에 의한 전달'방식을 택하는 편이 대개 낫다

Jangmanbo·2023년 4월 9일
0

Effective C++

목록 보기
20/33

pass by value

C++은 함수로부터 객체를 전달받거나 함수에 객체를 전달할 때 기본적으로 값에 의한 전달(pass by value) 방식을 사용한다. 함수의 매개변수는 실제 인자의 사본을 통해 초기화되며, 어떤 함수를 호출한 쪽은 그 함수가 반환한 값의 사본을 돌려받는다.

이 사본은 복사생성자가 만들어내는데, 이 pass by value는 고비용의 연산이 되기도 한다.

class Person {
public:
	Person();			// 예시이므로 매개변수 생략
    virtual ~Person();	// 가상 소멸자. 항목 7 참고
    ...
private:
	string name;
    string address;
};

class Student {
public:
	Student();
    virtual ~Student();
    ...
private:
	string studentName;
    string studentAddress;
};

/***************************************************/

bool validateStudent(Student s);	// Student 객체를 값으로 전달받는 함수

Student plato;
bool platoIsOK = validateStudent(plato);	// validateStudent 호출

plato로부터 매개변수 s를 초기화시키기 위해 Student복사 생성자가 호출되며, validateStudent가 끝날 때 s를 소멸시키기 위해 Student소멸자가 호출된다.

심지어 Student 객체에는 두 개의 string 객체 멤버변수가 존재하므로 Student객체가 생성될 때마다 두 개의 String 객체가 생성된다. 또한 StudentPerson의 파생클래스이므로 Student가 생성되기 전에 Person이 먼저 생성된다. 이때 Person에도 두 개의 String 객체 멤버변수가 존재하므로 두 개의 String 객체가 생성된다.


정리하자면

  1. Student 복사 생성자 1번
  2. Person 복사 생성자 1번
  3. String 복사 생성자 4번

추가로 소멸자도 복사 생성자의 횟수만큼 불릴 것이다.
즉, Student 객체를 pass by value로 전달하니 생성자 6번, 소멸자 6번의 비용이 든다.


reference to const

장점 1. 생성자와 소멸자를 호출하지 않아 효율적

생성자, 소멸자 호출을 몇 번씩 거치지 않고도 객체를 넘길 수 있는 방법이 있다. 바로 상수객체에 대한 참조자(reference to const)로 전달하는 것이다.

bool validateStudent(const Student& s);

pass by value로 구현한 코드보다 훨씬 효율적이다. 새로 만들어지는 객체가 없기 때문에 생성자와 소멸자가 전혀 호출되지 않기 때문이다.

const를 주목하자. pass by value일 때는 validateStudentStudent의 사본을 사용했지만, 참조에 의한 전달로 바뀌면서 Student 객체가 바뀔 가능성이 생기기 때문에 const가 붙게 되었다.

장점 2. 복사손실 문제(slicing problem) 해결

파생 클래스 객체가 기본 클래스 객체로서 전달되는 경우가 있는데, 이때 이 객체가 pass by value로 전달되면 기본 클래스의 복사 생성자가 호출되어 파생 클래스 객체 부분이 잘려나가게 된다.

class Window {
public:
	...
    string name() const;	// 윈도우의 이름 반환
    virtual void display() const;	// 윈도우를 화면에 그림
};

class WindowWithScrollBars: public Window {
public:
	...
    virtual void display() const;	// Window 클래스보다 더 좋게 그림 (아마도 스크롤바를 추가하지 않을까?)
};

/***************************************************/

// pass by value
void printNameAndDisplay(Window w)
{
	cout << w.name();
    w.display();
}

WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

매개변수 wWindow 생성자에 의해 생성되기 때문에 기존에 가지고 있던 WindowWithScrollBars만의 정보는 잘려나가게 된다. 따라서 printNameAndDisplay에서 호출하는 display는 어떤 상황에서든 Window 클래스의 display이다.

// reference to const
void printNameAndDisplay(const Window& w)
{
	cout << w.name();
    w.display();
}

반면 w를 reference to const로 전달하면 이러한 slicing problem을 해결할 수 있다.


부록

참조자는 포인터를 써서 구현된다. 즉, 참조자를 전달한다는 것은 포인터를 전달한다는 것이다.

전달하는 객체의 타입이 기본제공 타입(ex. int)일 경우 참조자보다 값으로 넘기는 것이 효율적인 경우가 많다. 이는 STL의 반복자와 함수 객체도 마찬가지이다.



정리

  • pass by value보다는 reference to const를 선호하자. 대체로 효율적이며 slicing problem을 막아준다.
  • 단, 기본제공 타입, STL 반복자, 함수 객체 타입에는 pass by value가 적절하다.

0개의 댓글