스토리텔링 개발자

[Effective C++] 20. const & 전달하기 본문

개발/Effective C++

[Effective C++] 20. const & 전달하기

김디트 2024. 6. 13. 10:57
728x90

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

 

 

 

값에 의한 전달
  • C++는 함수로부터 객체를 전달받거나 함수에서 객체 전달시 ‘값에 의한 전달(pass-by-value)’ 방식을 사용한다.
  • 이는 c에서 물려받은 특성 중 하나이다.
  • 즉 특별히 다른 방식을 지정하지 않는 한, 함수 매개변수나 함수 반환값은 '사본'이다.

 

 

값에 의한 전달은 고비용이다.
  • class Person
    {
    ...
    private:
        string name;
        string address;
    };
    
    class Student : public Person
    {
    ...
    private:
        string schoolName;
        string schoolAddress;
    };
    
    bool validateStudent(Student s);
    ..
    Student plato;
    bool platoIsOK = validateStudent(Plato);
  • validateStudent 함수에 매개변수를 전달하는 비용
    • 전달 시 Student 복사 생성자 호출
    • 함수 리턴 시 생성한 임시 매개변수의 Student 소멸자 호출.
    • 허나 객체 내부에서 Student의 string 객체와 Person의 string 객체들도 덩달아 복사 생성 / 소멸하므로 비용은 더 증가한다.
  • 해결 방법 : 매개 변수로 상수 객체에 대한 참조자(reference to const)를 전달해준다.
    • bool validateStudent(const Student& s);
    • const를 붙임으로써 매개변수로 사용될 때 변화되지 않음을 보장받을 수 있다.
    • 참조자를 호출하므로 생성자 / 소멸자 호출이 없다.
    • 추가로, 복사 손실 문제(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 b;
    printNameAndDisplay(b); // 복사 손실 문제 발생!
    // WindowWithScrollBars의 display가 아니라 Window의 display가 호출됨.
    // void printNameAndDisplay(const Window& w) 로 변경하면 해결된다.

 

 

 

참조자의 특성
  • 참조자는 포인터를 써서 구현이 된다.
    • 그러므로 타입이 기본 제공 타입(int 등)일 경우 값으로 넘기는 편이 되려 효율적이다.
    • 포인터의 크기와 기본 제공 타입의 크기가 그다지 큰 차이가 없기 때문이다.

 

 

 

반복자와 함수 객체를 구현할 때 명심할 점
  1. 복사 효율을 높일 것
  2. 복사 손실 문제에 노출되지 않도록 할 것

 

 

 

타입 크기가 작다고 무조건 값으로 전달하면 안 되는 이유
  1. 복사 생성자가 비쌀 수 있다.
    • 데이터 멤버가 포인터 하나라도 포인터 멤버가 가리키는 대상까지 복사하는 작업도 따라온다. 
  2. 수행 성능 문제가 있을 수 있다.
    • 예를 들면 컴파일러 중엔 기본 제공 타입과 사용자 정의 타입을 아예 다르게 취급하는 것도 있다.
    • 심지어 기본제공 타입과 사용자 정의 타입의 하부 표현구조가 같아도 그럴 수 있다는 것이 문제이다.
    • 진짜 double은 레지스터에 적재하나, double 하나로만 구성된 객체는 레지스터 적재를 하지 않는다던가 하는 식이다. 하지만 포인터는 확실히 레지스터에 적재되므로 이 경우 참조 전달을 쓰는 게 낫다.
  3. 사용자 정의 타입은 변화에 노출되어 있다
    • 언제든 구현이 변화할 수 있는 여지가 있다.
  • 따라서 ‘값에 의한 전달’이 저비용이라 확실히 가정할 수 있는 타입들은 다음 세 가지 뿐이다.
    • 기본 제공 타입
    • STL 반복자
    • 함수 객체 타입

 

728x90
Comments