ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 업 캐스팅 슬라이싱..
    @ 16. 1 ~ 17. 1/C++ 2014. 6. 28. 19:05

    업 캐스팅시 슬라이싱이 발생하게 되는데..

    class Super
    {
    public:
     Super(int i)
     {
      cout << "Super"<< endl;
      n=i;
     }
     virtual void show()
     {
      cout << "Super Show"<<endl;
     }
    public:
     int n;
    };

    class Sub : public Super
    {
    public:
     Sub() : Super(1)
     {
      cout << "Sub" << endl;
     }
     virtual void show()
     {
      cout << "Show Sub"<<endl;
     }
     void SubSub()
     {
      cout <<"Only Sub"<< endl;
     }
     int num;
    };

    int main()

     Sub sb;
     Super &ptr=sb;
     ptr.show();
     
     Sub pb;
     Super pptr=pb;
     pptr.show();

    결과는

    ptr은 ShowSub

    pptr은 SuperShow

    그래서 슬라이싱 방지를 위해선 참조나 포인터로 해야한다.

     


    아래는 관련된 글..


     

    이번 Item은 pass-by-value보다 pass-by-reference-to-const를 사용해야 하는 이유를 설명합니다.

    디폴트로, C++은 객체들을 함수로 또는 함수로부터 값에 의해 전달합니다 (C에서 물려 받은 특징). 다르게 규정하지 않는 한, 함수 파라미터들은 실제 인수들의 복사본들로 초기화되며, 함수 호출자들은 그 함수에 의해 리턴되는 값의 복사본을 돌려 받게 됩니다. 이러한 복사본들은 객체의 복사 생성자들에 의해 만들어 집니다. 이는 pass-by-value를 값비싼 연산으로 만들 수 있습니다. 예를 들어, 다음의 클래스 계층을 고려해 보죠:

    class Person {
    public:
      Person();                          // parameters omitted for simplicity
      virtual ~Person();                 // see Item 7 for why this is virtual
      ...
    private:
      std::string name;
      std::string address;
    };

    class Student: public Person {
    public:
      Student();                         // parameters again omitted
      ~Student();
      ...
    private:
      std::string schoolName;
      std::string schoolAddress;
    };


    이제 (by value로) Student 인수를 가지며 검증된 결과를 리턴하는 validateStudent 함수를 생각해 봅니다:

    bool validateStudent(Student s);           // function taking a Student by value
    Student plato;                             // Plato studied under Socrates
    bool platoIsOK = validateStudent(plato);   // call the function


    이 함수가 호출될 때 어떤 일이 발생할까요?

    명백히, Student 복사 생성자가 plato로부터 매개변수 s를 초기화하기 위해 호출됩니다. 또 명백히, s는 validateStudent가 리턴될 때 소멸됩니다. 이 함수의 파라미터 전달 비용은 Student 복사 생성자 호출하는 것 하나와 Student 소멸자에 대한 호출 하나입니다.

    하지만, 이것이 이야기의 끝이 아닙니다. Student 객체는 내부에 두 개의 string 객체들을 가지고 있기 때문에, Student 객체를 생성할 때마다 두 string 객체들 또한 생성해야 합니다. 또한, Student 객체는 Person 객체에서 상속하기 때문에, Student 객체를 생성할 때마다 Person 객체 또한 생성해야 합니다. Person 객체는 두 개의 추가 stirng 객체를 내부에 가지고 있기 때문에 Person 생성은 두 개의 string 생성을 유발합니다. Student 객체를 by value로 전달하는 최종 결과는 하나의 Sudent 복사 생성자, 하나의 Person 복사 생성자 및 네 번의 string 복사 생성자 호출하는 것입니다. Student 객체가 소멸될 때에 모든 생성자 호출은 소멸자 호출에 매칭되기 때문에 Student를 by value를 전달하는 전체 비용은 6 개의 생성자들과 소멸자들입니다!

    이제, 이 모든 생성과 소멸을 우회할 수 있는 방법을 찾는 것이 당연합니다. pass-by-reference-to-const가 해결책입니다:

    bool validateStudent(const Student& s);


    이는 훨씬 더 효율적입니다: 새로운 객체가 생성되지 않기 때문에 생성자/소멸자 호출이 없습니다. 개선된 파라미터 선언에서 const는 중요합니다. 원래 버전의 validateStudent는 Student를 by value 파라미터로 가졌기 때문에, 호출자들은 이 함수가 전달되는 Student에 대한 어떤 수정으로부터도 보호된다는 것을 압니다; validateStudent는 오직 이 것의 복사본을 수정할 뿐입니다. 이제 Student가 by reference로 전달되면 이를 const로 선언할 필요가 있습니다. 만약 const로 선언하지 않는다면 validateStudent가 전달된 Student에 대해 수정하는 것에 고민해야 합니다.

    by reference로 파라미터들을 전달하는 것은 slicing 문제를 회피할 수 있습니다. 파생 클래스 객체가 (by value에 의해) 기저 클래스로 전달될 때, 기저 클래스의 복사 생성자가 호출되며, 이 객체가 파생 클래스 객체로 동작하도록 만드는 특별한 피쳐들이 잘려나가게 됩니다. 단순히 기저 클래스만 남게 되며, 이는 기저 클래스 생성자로 만들었기 때문에 놀랄 일도 아닙니다. 이는 절대 여러분이 원하는 것은 아닙니다. 예를 들어, 그래픽 윈도우 시스템의 구현을 위한 일련의 클래스들을 작업하고 있다고 가정해 보죠:
     

    class Window {
    public:
      ...
      std::string name() const;           // return name of window
      virtual void display() const;       // draw window and contents
    };
    class WindowWithScrollBars: public Window {
    public:
      ...
      virtual void display() const;
    };


    모든 윈도우 객체들은 이름을 가지며 name 함수를 통해서 얻을 수 있으며, 모든 윈도우들은 화면상에 표시될 수 있으며 display 함수를 호출하여 디스플레이 합니다. display가 가상이라는 사실에서 단순한 기저 클래스 Window 객체들이 디스플레이되는 방법은 더 장식적인 WindowWithScrollBar들이 디스플레이되는 방법과는  다를 것 같다는 것을 여러분에게 말해 줍니다.

    이제 윈도우의 이름과 이 윈도우를 디스플레이하는 함수를 작성한다고 가정합니다. 아래는 이런 함수를 작성하는 잘못된 방법입니다:

    void printNameAndDisplay(Window w)         // incorrect! parameter may be sliced!
    {                                          
      std::cout << w.name();
      w.display();
    }


    이 함수를 WindowWithScrollBars 객체로 호출했을 때 어떤 일이 발생하는지 고려해 보죠:

    WindowWithScrollBars wwsb;
    printNameAndDisplay(wwsb);


    인수 w는 Window 객체로 생성될 것이며, wwsb를 WindowWithScrollBars 객체들로 동작하게 만든 특정 정보들은 잘려나갈 것입니다. printNameAndDisplay 내에서, w는 함수에 전달되는 객체의 타입에 상관없이 항상 Window 클래스 객체로서 동작할 것입니다.

    이 슬라이싱 문제를 우회하는 방법은 w를 reference-to-const로 전달하는 것입니다:

    void printNameAndDisplay(const Window& w)   // fine, parameter won't be sliced
    {                                           
      std::cout << w.name();
      w.display();
    }


    이제 w는 실제로 전달되는 윈도우의 종류에 따라 동작합니다.

    만약, 여러분이 C++ 컴파일러의 후드 밑을 옅본다면, 레퍼런스들은 대개 포인터들로 구현되었음을 발견할 것이며, 레퍼런스로 어떤 것을 전달하는 것은 대개 실제로는 포인터를 전달하는 것입니다. 결과적으로 만약 (int와 같은) 내장 타입의 객체를 가지고 있다면, by value로 전달하는 것이 by reference로 전달하는 것보다 대개 효율적입니다. 즉, 내장 타입들에 대해서는 pass-by-value와 pass-by-reference-to-const를 선택할 수 있을 때에, pass-by-value를 선택하는 것이 비합리적인 것은 아닙니다. 같은 조언이 STL에서 이터레이터들과 함수 객체들에도 적용됩니다. 개념상 이들은 by value로 전달되도록 설계되었기 때문입니다.

    내장 타입들은 작기때문에, 일부 사람들이 모든 작은 타입들이 심지어 사용자 정의 타입에 대해서도 pass-by-value를 위한 좋은 후보들로 결론짓습니다. 한 객체가 작다고 해서 이 것의 복사 생성자가 값싸다는 것을 의미하지 않습니다. 많은 객체들 (이들중 대부분의 STL 컨테이너들이) 하나의 포인터 이상을 포함하지 않지만, 이런 객체들에 대한 복사는 이들이 포인팅하는 모든 것을 복사하도록 유발합니다. 이는 매우 값비쌀 수 있습니다.

    심지어 작은 객체들이 값싼 복사 생성자들을 가지더라도, 성능 문제들이 있을 수 있습니다. 몇몇 컴파일러들은 내장과 사용자 정의 타입들이 심지어 같은 표현을 내포하더라도 서로 다르게 취급합니다. 예를 들어, 몇몇 컴파일러들은 naked double들은 기쁜마음으로 정기적으로 레지스터에 넣으면서도, double로만 구성된 객체를 레지스터에 넣는 것을 거부합니다. 이러한 일이 발생할 때에는, 이 객체들을 by reference로 전달하는 것이 더 낫습니다. 컴파일러들은 확실히 (레퍼런스들에 대한 구현인) 포인터들은 레지스터들에 넣을 것이기 때문입니다.

    작은 사용자 정의 타입들이 항상 pass-by-value 후보일 필요가 없는 또 하나의 이유는, 사용자 정의이기 때문에, 이들의 크기는 변하기 쉽습니다. 현재의 작은 타입이 미래 릴리즈에서는 내부 구현이 수정되어 더 커질 수 있습니다. 다른 C++ 구현으로 전환하면 상황은 더 크게 변합니다. 예를 들어, 표준 라이브러리의 string 타입은 다른 타입들에 비해 7 배나 더 큽니다.

    대개, pass-by-value가 합리적으로 값싼 타입은 오직 내장 타입들 및 STL 이터레이터와 함수 객체 타입들입니다. 이외의 모든 경우에서 이 Item의 조언을 따라서 pass-by-value보다 pass-by-reference-to-const를 따릅니다.

    기억할 것들

    • pass-by-value 보다 pass-by-reference-to-const를 선호합니다. 이는 대개 보다 효율적이며 슬라이싱 문제를 회피합니다.
    • 이 규칙은 내장 타입들과 STL 이터레이터와 함수 객체 타입들에는 적용되지 않습니다. 이들에게는 대개 pass-by-value가 보다 적합합니다.

    저작자 표시 비영리
Designed by Tistory.