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

업데이트:     Updated:

카테고리:

태그:

이 글은 아래의 책을 자세히 정리한 후, 정리한 글을 GPT에게 요약을 요청하여 작성되었습니다.
이펙티브 C++ 제3판, 스콧 마이어스 저자, 곽용재 번역

📦 4. 설계 및 선언

👉🏻 항목 20: ‘값에 의한 전달’보다는 ‘상수객체 참조자에 의한 전달’ 방식을 택하는 편이 대개 낫다

📌 객체의 전달

C++은 기본적으로 함수에 객체를 전달하거나 받을 때, ‘값에 의한 전달(pass-by-value)’ 방식을 사용한다.

따로 지정하지 않으면 아래처럼 동작한다:

  • 객체를 전달할 때 → 매개변수는 실제 인자의 복사본으로 초기화된다.
  • 객체를 전달받을 때 → 함수가 반환한 복사본을 돌려받는다.

이 복사 과정에서는 복사 생성자가 호출되고, 이게 꽤 비쌀 수 있다.

class Person {
public:
	Person();
	virtual ~Person();
	...
private:
	string name;
	string address;
};

class Student : public Person {
public:
	Student();
	~Student();
	...
private:
	string schoolName;
	string schoolAddress;
};
bool validateStudent_Val(Student s);
bool validateStudent_Ref(const Student& s);

int main() {
	Student plato;
	bool platoIsOK = validateStudent_Val(plato);
	bool platoIsOK = validateStudent_Ref(plato);
}

1. validateStudent_Val(plato); 호출 시

  • 복사 생성자 호출:
    1. Student 복사 생성자
    2. Person 복사 생성자
    3. Person 내 string name/address 복사 생성자
    4. Student 내 string schoolName/schoolAddress 복사 생성자

      → 총 6번의 생성자 호출

  • 함수 종료 시 소멸자 호출:
    1. Student 소멸자
    2. Student 내 schoolName/schoolAddress 소멸자
    3. Person 내 string name/address 소멸자
    4. Person 소멸자

      → 총 6번의 소멸자 호출

→ 결론: 동작은 맞지만 비효율적이다.


2. validateStudent_Ref(plato); 호출 시

const 참조로 전달되기 때문에 복사 생성자가 호출되지 않는다.
실제로는 포인터를 넘기는 것과 거의 동일하게 작동.

게다가 const 덕분에 함수 내부에서 객체가 수정되는 것도 막을 수 있다.



✅ 참조에 의한 전달 방식의 장점

  • 복사 자체가 생략되므로 비용이 적다.
  • 복사손실(slicing) 을 막을 수 있다.


예를 들어 보자:

class Window {
public:
	...
	string name() const;
	virtual void display() const;
};

class WindowWithScrollBars : public Window {
public:
	...
	virtual void display() const;
};
void printNameAndDisplay_Val(Window w) {
	cout << w.name();
	w.display();
}

void printNameAndDisplay_Ref(const Window& w) {
	cout << w.name();
	w.display();
}

int main() {
	WindowWithScrollBars wwsb;
	printNameAndDisplay_Val(wwsb);
	printNameAndDisplay_Ref(wwsb);
}

1. printNameAndDisplay_Val(wwsb);

Window w = wwsb;

이런 식으로 기본 클래스(Window) 객체에 복사되므로,
파생 클래스(WindowWithScrollBars)의 기능은 모두 사라진다.
w.display()는 기본 클래스 버전이 호출됨.

2. printNameAndDisplay_Ref(wwsb);

참조로 전달되기 때문에 원래 타입을 유지하며,
w.display()파생 클래스의 버전이 호출된다.



📌 참조자 전달을 선호하는 이유

  • 기본제공 타입은 값으로 전달해도 부담이 없다. (ex. int, double)
  • 하지만 사용자 정의 타입은 내부적으로 무거운 복사를 유발할 수 있다.
  • 심지어 double 하나로 만든 클래스라도 컴파일러가 레지스터에 안 얹어줄 수도 있다.
  • 사용자 정의 타입의 구조는 언제든지 변할 수 있다.
  • 그러니 값에 의한 전달이 적절한 경우는 기본제공 타입, STL 반복자, 함수 객체 타입 정도다.



🧐 정리

  • 대부분의 경우, 값에 의한 전달보다 상수 참조자 전달이 효율적이다.
    게다가 복사손실(slicing) 문제도 예방할 수 있다.

  • 기본제공 타입, STL 반복자, 함수 객체 타입에 대해서만 값에 의한 전달이 더 낫다.
    나머지는 참조자에 의한 전달을 사용하자.

Cpp 카테고리 내 다른 글 보러가기

댓글남기기