여담으로.. 가장 쉽게 적용할 수 있으며 효율이 좋은 단원이라고 생각한다. 결론부터 말하자면 상수,반복자,함수 객체를 제외한 모든 매개 변수에 대해 const &를 붙이자
1. 값에 의한 전달의 고비용 연산
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 schooAddress;
};
bool validateStudent(Student s);
Student plato;
bool platoIsOk = validateStudent(plato);
위의 예제에서 validateStudent 함수가 사용되면서 한 번의 복사 생성과 소멸자 호출이 일어난 것처럼 보이지만, Student에 string이 2개.. 그리고 Person으로 부터 파생 되었기에 Person의 string도 2개 (앞의 string보다 먼저 생성), 자식 부모간 복사 생성 2번으로 총 6번의 복사 생성과 덤으로 6번의 소멸자가 호출된다.
대부분 커스텀 객체가 이보다 더 하면 더 했지 못하지 않았을테니 값에 의한 전달이 굉장히 비효율적이며 고비용이라는 것을 알 수 있다.
2. const &(상수객체에 대한 참조자)를 통한 해결
가장 앞에서 언급했듯이 이 장의 가장 좋은 해결책이라고 보면 된다. 새로 만들어지는 객체 같은 것이 없기때문에, 생성자와 소멸자가 전혀 호출되지 않는다. 참고로 const를 꼭 붙여야 한다는 사실을 잊지 말자. 기존의 값에 의한 전달은 넘겨주는 것이 결국 복사된 객체(사본)이기 때문에 내부에서 어떻게 하든 전혀 상관이 없었지만, 지금 매개변수로 넘겨주는 것은 참조자이다. 즉, 값이 변경될 가능성이 농후한 (오히려 그러려고 쓰는 문법) 녀석이기에 const를 붙여 기존의 사본을 넘겨주는 방식처럼 값의 변경을 막아줘야 한다.
bool validateStudent(const Student& s);
3. const &를 이용한 복사손실 문제 해결
정말 대단한 문법이 아닌가 싶다. 하나의 문제 해결 뿐만 아니라, 무려 두 가지나 해결해 주다니.. 먼저, 복사손실이 무엇인지 알아보자.
class Window{
public:
...
std::string name()const;
virtual void display()const;
};
class WindowWithScrollBars : public Window{
public:
...
virtual void display() const;
};
...
void printNameAndDisplay(Window w)
{
std::cout << w.name();
w.display();
}
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);
코드를 작성하다 보면 비일비재하게 부모 타입에 자식 타입 객체를 넘겨 주는 상황이 생긴다. 위의 코드에서는 자식 객체인 WindowWithScrollBars를 Window 형식의 매개 변수로 전달하고 있다. 이 때 발생하는 문제점은 자식 객체의 부속 정보가 전부 싹둑 잘려져 나가게 된다. 따라서 자식 객체의 display() 함수는 어떤 방식을 사용하더라도 밖으로 튀어 나올수가 없게 된다.
해결책은 이미 제시했다. const & (상수객체에 대한 참조자)를 이용하면 된다.
void printNameAndDisplay(const Window& w){
std::cout << w.name();
w.display();
}
4. const &를 사용하지 않아도 될 때
결론을 먼저 말하자면 STL의 반복자,원시 자료형(int,double...), 함수 객체를 제외한 모든 곳에 const &를 붙이자.
크기가 작은 객체에 대해 막연히 const &를 사용하지 않는 것은 미련한 짓이다. 만약 클래스에 포인터 하나만 존재한다 하더라도 그 포인터가 가리키는 대상까지 복사된다는 사실을 잊지말아야 하며 사용자 임의 객체는 나중에 수정되어 그 크기가 지금보다 커질 것을 고려해야 한다. 마지막으로 컴파일러에 따라서 원시 자료형과 그 하부 표현구조가 일치하는 사용자 임의 객체를 다르게 인식할 수 있다는 것이다. 즉 double 같은 것들은 레지스터에 확실히 들어가지만 double을 들고 있는 사용자 임의 객체는 레지스터에 넣지 않을 수 있다는 얘기다. 하지만 const &를 사용하면 &내부가 포인터로 만들어졌기 때문에 레지스터에 들어가는 것을 보장할 수 있다.
이러쿵 저러쿵 여러요인을 따지며 설명했지만 압축해서 낸 결론은 하나다.
'원시자료형,STL 반복자,함수 객체를 제외하고 값에 의한 전달은 모~두 상수 객체 참조자에 의한 전달로 바꾸자!'
이것만은 잊지 말자!
- '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달'을 선호합시다. 대체적으로 효율적일뿐만 아니라 복사손실 문제까지 막아 줍니다.
- 이번 항목에서 다룬 법칙은 기본제공 타입 및 STL 반복자, 그리고 함수 객체 타입에는 맞지 않습니다. 이들에 대해서는 '값에 의한 전달'이 더 적절합니다.
'C++ > Effective C++' 카테고리의 다른 글
[Effective C++] 22. 데이터 멤버가 선언될 곳은 private 영역임을 명시하자 (0) | 2020.08.29 |
---|---|
[Effective C++] 21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자 (0) | 2020.08.27 |
[Effective C++] 19. 클래스 설계는 타입 설계와 똑같이 취급하자. (0) | 2020.08.06 |
[Effective C++] 18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자 (0) | 2020.08.06 |
[Effective C++] 17. new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자 (0) | 2020.08.03 |