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

수민이슈·2023년 3월 24일
0

Effective C++

목록 보기
20/30
post-thumbnail

스콧 마이어스의 Effective C++을 읽고 개인 공부 목적으로 요약 작성한 글입니다!

💡 '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달'을 선호하자!
: 대체적으로 효율적이고, 복사손실 문제를 방지해준다.
💡 기본 제공 타입, STL 반복자, 함수 객체 타입에는 '값에 의한 전달'이 더 적합하다!


🖊️ '값에 의한 전달'과 '상수 객체 참조자에 의한 전달'

'값에 의한 전달'

함수로부터 객체를 전달받거나, 함수에 객체를 전달할 때 '값에 의한 전달 (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(Student s);

Student plato;
bool platoIsOK = validateStudent(plato);

이런 상황에서,
plato로부터 매개변수 s를 초기화 시키기 위해 Student의 복사 생성자가 호출된다.
s는 validateStudent가 종료되면 소멸된다.

그래서 이 상황에서 Student의 복사 생성자 한 번, 소멸자가 한 번 호출된다.

근데 거기다가,
Student 객체에 string 객체 2개의 복사 생성자,
Student는 Person을 상속받은 객체이므로 Person의 복사 생성자, Student의 string 객체 2개의 복사 생성자
가 호출될거다.
당연히 소멸자도 그만큼..

결국
위 상황에서
생성자 6번, 소멸자 6번이 호출된다.
어마무시하다 ,,,

근데 이걸 상수 객체에 대한 참조자로 전달하도록 바꾸면 어케될까용?

'상수 객체 참조자에 의한 전달'

bool validateStudent(const Student& s);

이렇게.

그러면?
객체가 없기 때문에 생성자, 소멸자가 전혀 호출되지 않으요.

왜 const가 붙나욤

원래 상황에서,
validateStudent는 Student 매개변수를 값으로 받으니까
함수로 전달된 Student 객체에 어떤 변화가 생기더라도 변화로부터 안전하징.
validateStudent에서 주무르는 객체는 사본이니까.

근데 이 상황에서는
const를 붙이지 않으면 인자로 넘어간 Student 객체가 변경될 수도 있음.

복사손실 문제 (slicing problem)

파생 클래스 객체가 기본 클래스 객체로서 전달되는 경우가 있다
이 때 값으로 전달되면,
기본 클래스의 복사 생성자가 호출되는데,, 파생 클래스 객체로 동작하게 해주는 특징이 잘린다
그니까 걍 파생클래스라는 이름만 달고 있는 기본 클래스 객체가 되는 거지

예를들면

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

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

void printNameAndDisplay(Window w) {
	cout << w.name();
    w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

이 상황에서
display()는 가상함수니까,
printNameAndDisplay()에서
WindowWithScrollBars 객체를 넣었음 그니까 WindowWithScrollBars의 display가 불려야겠지 당연히

근데
printNameAndDisplay()가 인자로 받는건 Window타입이다
그래서 w가 어떤 타입이든 상관없이 Window로 받아버림 (Window 생성자를 호출하니까)
그래서 결국
호출되는 display()는 Window의 display().

이게 바로 복사손실 문제.

그래서 이걸 해결하기 위해서

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

이렇게 하자.
그러면? w에 어떤 종류의 Window 계통이 넘어오더라도 그 윈도우의 성질을 다 담을 수 이씀.


🖊️ 무조건 '상수 객체 참조자에 의한 전달'만 효율적인가유?

'값에 의한 전달'이 효율적인 경우

기본 제공 타입

왜냐면
참조자를 전달한다는 것은 포인터를 전달한다는 것과 같음
그래서 전달하는 객체의 타입이 기본 제공 타입인 경우에는 값에 의한 전달이 효율적일 때가 많다

STL 반복자

함수 객체

그리고 STL 반복자, 함수 객체에도 마찬가지당.
왜냐하면
반복자, 함수 객체는
복사 효율을 높일 것
복사손실 문제에 노출되지 않을 것
을 전제로 만들어졌기 때문이당.

기본 제공 타입은 크기가 작고, 복사 생성자 호출이 저비용이다.

어쨌든 위 세가지 빼고는 참조에 의한 전달을 하자

위 세가지를 빼고

아무리 객체의 크기가 작고
복사 생성자 호출 비용이 적더라도,
수행 성능 문제가 발생할 수 있다.

그니까 기본 제공 타입은 레지스터에 넣어주고,
똑같은데 사용자 정의 타입은 레지스터에 안넣어주기도 한다.

그리고
사용자 정의 타입의 크기는 언제든지 변할 수 있따.
구현 환경마다 바뀔수도 있다.


😊

무조건
1. 기본 제공 타입
2. STL 반복자
3. 함수 객체
빼고는 상수 객체 참조자에 의한 전달을 사용하자
오키!

0개의 댓글