수색…


다형성 클래스 정의

전형적인 예는 사각형, 원 및 다른 구체적인 모양으로 파생 될 수있는 추상 모양 클래스입니다.

부모 클래스 :

다형성 클래스부터 시작해 보겠습니다.

class Shape {
public:
    virtual ~Shape() = default;
    virtual double get_surface() const = 0;
    virtual void describe_object() const { std::cout << "this is a shape" << std::endl; }  

    double get_doubled_surface() const { return 2 * get_surface(); } 
};

이 정의를 읽는 방법?

  • virtual 함수 키워드를 사용하여 소개 된 멤버 함수를 통해 다형성 동작을 정의 할 수 있습니다. 여기서 get_surface()describe_object() 는 분명히 원 대신 사각형에 대해 다르게 구현됩니다. 함수가 객체에서 호출되면 객체의 실제 클래스에 해당하는 함수가 런타임에 결정됩니다.

  • 추상 모양에 대해 get_surface() 를 정의하는 get_surface() 의미가 없습니다. 이것이 함수 뒤에 = 0 이 오는 이유입니다. 이것은 함수가 순수 가상 함수 임을 의미합니다.

  • 다형성 클래스는 항상 가상 소멸자를 정의해야합니다.

  • 비 가상 구성원 함수를 정의 할 수 있습니다. 이 함수가 객체에 대해 호출 될 때 컴파일 타임에 사용되는 클래스에 따라 함수가 선택됩니다. 여기서 get_double_surface() 는 이런 방식으로 정의됩니다.

  • 하나 이상의 순수 가상 함수를 포함하는 클래스는 추상 클래스입니다. 추상 클래스는 인스턴스화 될 수 없습니다. 포인터 또는 추상 클래스 유형의 참조 만 가질 수 있습니다.

파생 클래스

일단 다형 기본 클래스가 정의되면이를 파생시킬 수 있습니다. 예 :

class Square : public Shape {
    Point top_left;
    double side_length;
public: 
    Square (const Point& top_left, double side)
       : top_left(top_left), side_length(side_length) {}

    double get_surface() override { return side_length * side_length; }   
    void describe_object() override { 
        std::cout << "this is a square starting at " << top_left.x << ", " << top_left.y
                  << " with a length of " << side_length << std::endl; 
    }  
};

몇 가지 설명 :

  • 부모 클래스의 가상 함수를 정의하거나 재정의 할 수 있습니다. 부모 클래스에서 함수가 가상이라는 사실은 파생 클래스에서 가상으로 만듭니다. 컴파일러에게 키워드 virtual 다시 말할 필요가 없습니다. 그러나 함수 선언의 주목할만한 변화로 인해 발생하는 미묘한 버그를 방지하기 위해 함수 선언 끝에 키워드 override 를 추가하는 것이 좋습니다.
  • 부모 클래스의 모든 순수 가상 함수가 정의되면이 클래스의 객체를 인스턴스화 할 수 있습니다. 그렇지 않으면 추상 클래스가됩니다.
  • 모든 가상 기능을 재정의 할 의무가 없습니다. 필요에 따라 부모의 버전을 유지할 수 있습니다.

인스턴스화의 예

int main() {

    Square square(Point(10.0, 0.0), 6); // we know it's a square, the compiler also
    square.describe_object(); 
    std::cout << "Surface: " << square.get_surface() << std::endl; 

    Circle circle(Point(0.0, 0.0), 5);

    Shape *ps = nullptr;  // we don't know yet the real type of the object
    ps = &circle;         // it's a circle, but it could as well be a square
    ps->describe_object(); 
    std::cout << "Surface: " << ps->get_surface() << std::endl;
}

안전한 다운 캐스팅

다형성 클래스의 객체에 대한 포인터가 있다고 가정 해보십시오.

Shape *ps;                       // see example on defining a polymorphic class
ps =  get_a_new_random_shape();  // if you don't have such a function yet, you 
                                 // could just write ps = new Square(0.0,0.0, 5);

다운 캐스트는 일반적인 다형성 Shape 에서 Square 또는 Circle 과 같이 파생 된보다 구체적인 모양으로 캐스팅됩니다.

왜 추락 하는가?

대부분의 경우, 가상 함수는 유형과 상관없이 객체를 조작 할 수 있으므로 객체의 실제 유형을 알 필요가 없습니다.

std::cout << "Surface: " << ps->get_surface() << std::endl; 

다운 캐스트가 필요하지 않으면 디자인이 완벽 할 것입니다.

그러나 때때로 다운 캐스트가 필요할 수 있습니다. 일반적인 예는 하위 클래스에 대해서만 존재하는 비 가상 함수를 호출하려는 경우입니다.

예를 들어 서클을 고려해보십시오. 원은 지름이 있습니다. 따라서 클래스는 다음과 같이 정의됩니다.

class Circle: public Shape { // for Shape, see example on defining a polymorphic class
    Point center;
    double radius;
public: 
    Circle (const Point& center, double radius)
       : center(center), radius(radius) {}

    double get_surface() const override { return r * r * M_PI; }   

    // this is only for circles. Makes no sense for other shapes 
    double get_diameter() const { return 2 * r; }
};

get_diameter() 멤버 함수는 원에만 존재합니다. Shape 객체에는 정의되어 있지 않습니다.

Shape* ps = get_any_shape();
ps->get_diameter(); // OUCH !!! Compilation error 

다운 캐스트하는 방법?

ps 가 서클을 가리키는 지 확실히 알면 static_cast 선택할 수 있습니다.

std::cout << "Diameter: " << static_cast<Circle*>(ps)->get_diameter() << std::endl;

이것은 트릭을 할 것입니다. 그러나 매우 위험합니다. psCircle 이 아닌 다른 것으로 나타나면 코드의 동작이 정의되지 않습니다.

따라서 러시아어 룰렛을 사용하는 대신 dynamic_cast 안전하게 사용해야합니다. 특히 다형 클래스에 대한 것입니다.

int main() {
    Circle circle(Point(0.0, 0.0), 10);
    Shape &shape = circle;

    std::cout << "The shape has a surface of " << shape.get_surface() << std::endl;

    //shape.get_diameter();   // OUCH !!! Compilation error 

    Circle *pc = dynamic_cast<Circle*>(&shape); // will be nullptr if ps wasn't a circle 
    if (pc) 
        std::cout << "The shape is a circle of diameter " << pc->get_diameter() << std::endl;
    else
        std::cout << "The shape isn't a circle !" << std::endl; 
}        

다형성이 아닌 클래스에서는 dynamic_cast 를 사용할 수 없습니다. 클래스 나 부모 중 적어도 하나는 가상 함수를 사용할 수 있어야합니다.

다형성 및 소멸자

파생 된 인스턴스가 기본 포인터 / 참조로 저장되어있는 클래스를 다형 적으로 사용하려는 경우 기본 클래스의 소멸자는 virtual 이거나 protected 되어야합니다. 전자의 경우에는 객체 파괴가 vtable 을 검사하여 동적 유형에 따라 올바른 소멸자를 자동 호출합니다. 후자의 경우, 기본 클래스 포인터 / 참조를 통해 객체를 파기하는 것은 불가능하며 객체는 명시 적으로 실제 유형으로 처리 될 때만 삭제할 수 있습니다.

struct VirtualDestructor {
    virtual ~VirtualDestructor() = default;
};

struct VirtualDerived : VirtualDestructor {};

struct ProtectedDestructor {
  protected:
    ~ProtectedDestructor() = default;
};

struct ProtectedDerived : ProtectedDestructor {
    ~ProtectedDerived() = default;
};

// ...

VirtualDestructor* vd = new VirtualDerived;
delete vd; // Looks up VirtualDestructor::~VirtualDestructor() in vtable, sees it's
           // VirtualDerived::~VirtualDerived(), calls that.

ProtectedDestructor* pd = new ProtectedDerived;
delete pd; // Error: ProtectedDestructor::~ProtectedDestructor() is protected.
delete static_cast<ProtectedDerived*>(pd); // Good.

이러한 두 가지 방법 모두 파생 클래스의 소멸자가 파생 클래스 인스턴스에서 항상 호출되어 메모리 누수를 방지합니다.



Modified text is an extract of the original Stack Overflow Documentation
아래 라이선스 CC BY-SA 3.0
와 제휴하지 않음 Stack Overflow