반응형

다중 상속(Multiple Inheritance)

  • C++언어에서 2개 이상의 기반 클래스로 부터 상속 받을 수 있음
  • 다른 객체지향 언어에서는 허용되지 않는 경우가 많음
  • 기반 클래스의 멤버 이름이 동일한 경우 주의
#include <iostream>

struct A
{
    int a;
};
struct B
{
    int a;
};
struct C : public A, public B // 다중 상속
{
    int c;
};

int main()
{
    C c;
    // c.a = 10; 2개의 기반클래스에서 같은 멤버 이름을 사용하고 있으므로 ambiguous error
    c.A::a = 10; // 상속 받은 A 클래스의 a 멤버에 대입
    c.B::a = 20; // 상속 받은 B 클래스의 a 멤버에 대입
}

가상 상속(Virtual Inheritance)

  • 다이아몬드 처럼 다중 상속 기반 클래스가 같은 기반클래스를 상속 받아서 구현된 경우 동일 멤버를 이중 속상 받지 않도록 가상 상속을 사용해야 함

다이아몬드 상속

#include <iostream>

struct X
{
    int x;
};
struct A : public X // X의 멤버 x를 상속
{
    int a;
};
struct B : public X // X의 멤버 x를 상속
{
    int b;
};
struct C : public A, public B // 다중 상속
{
    int c;
};

int main()
{
    C c;
    c.x = 10; // 에러 X의 멤버인 x를 A, B 클래스가 상속받은 상태로 접근이 모호
}
#include <iostream>

struct X
{
    int x;
};
struct A : virtual public X // 가상 상속
{
    int a;
};
struct B : virtual public X // 가상 상속
{
    int b;
};
struct C : public A, public B // 다중 상속
{
    int c;
};

int main()
{
    C c;
    c.x = 10; // A, B를 가상 상속으로 바꿔주면 한곳에서 상속만 보장 되므로 해결
}
반응형

반응형
  • Down Casting
    • upcasting된 포인터를 원래의 타입으로 캐스팅 하는것
  • static_cast
    • 컴파일시간 캐스팅
    • 기반 클래스의 포인터가 실제 어떤 타입의 객체를 가리키는지 조사 할 수 없음
  • dynamic_cast
    • 실행시간 캐스팅
    • 잘못된 down casting 사용시 0 반환
    • 가상 함수가 없는 타입은 dynamic_cast를 사용할 수 없음
#include <iostream>
#include <typeinfo>
#include <typeindex>

class Animal 
{
public:
    virtual ~Animal() {}

};
class Dog : public Animal 
{
public:
    int color;
};

void foo(Animal* p)
{
    // 선 타입 비교, 후 static_cast
    if (typeid(*p) == typeid(Dog))
    {
        Dog* pDog = static_cast<Dog*>(p);
    }

    //OR

    // 선 dynamic_cast, 후 nullptr 비교
    Dog* pDog = dynamic_cast<Dog*>(p);
    if (pDog == nullptr)
    {
        std::cout << "nullptr" << std::endl;
    }

    std::cout << pDog << std::endl;

}

int main()
{
    Animal a; foo(&a);
    Dog d; foo(&d);
}

RTTI를 사용하지 않고 추가 기능을 제공하는 방법도 생각 필요

#include <iostream>

class Animal 
{
public:
    virtual ~Animal() {}
};
class Dog : public Animal
{
};

void foo(Animal* p)
{
}

void foo(Dog* p)
{
    foo(static_cast<Animal*>(p)); // 기존 공통 함수 기능 지원
    // Dog에 대한 특정 기능은 여기 추가 구현
}

int main()
{
    Animal a; foo(&a);
    Dog d; foo(&d);
}
반응형

반응형

가상 함수의 원리

  • 가상 함수가 1개 이상 포함 되어 있으면 컴파일러가 객체 주소에 가상 함수 테이블 포인터가 추가함
  • 가상 함수 테이블은 Class 별로 생성되어 RTTI 정보를 포함한 모든 가상함수 주소를 제공
  • 상속 받은 클래스의 가상 함수 테이블은 기본적으로 기반 클래스의 가진 가상 함수 주소를 가르키지만 파생 클래스에서 override하여 구현한 함수에 대해서는 구현한 가상함수의 주소를 가르킴
  • 가상함수 갯수가 많으면 오버헤드가 있음

가상함수 호출 원리

 

class Animal
{
    int age;
public:
    virtual ~Animal() {}
    virtual void foo() {}
    virtual void goo() {}
};

class Dog : public Animal
{
    int color;
public:
    virtual ~Dog() {}
    virtual void foo() override {}
};

int main()
{
    Animal a1, a2;
    Dog d1, d2;
    
    Animal* p = &d1;    
    p->foo(); // 내부적으로 p->vtptr[?]() 호출
}
반응형

반응형

static binding

  • 컴파일러가 컴파일 시간에 함수 호출을 결정
  • 포인터 타입으로 함수 호출을 결정
  • 빠름, 하지만, 비 이성적임
  • 이른 바인딩( Early Binding )
  • C++의 non-virtual function

 

dynamic binding

  • 실행 시간에 함수 호출을 결정
  • 포인터가 가리키는 메모리를 조사후 결정
  • 느림. 하지만, 이성적
  • 늦은 바인딩( Late Binding )
  • java, C++의 virtual function
// Static binding
class Animal
{
public:
    void Cry() { std::cout << "A Cry" << std::endl; }
};

class Dog : public Animal
{
public:
    void Cry() { std::cout << "D Cry" << std::endl; }
};

int main()
{
    Animal a;
    Dog d;

    Animal* p = &d;
    p->Cry();   // C++ : A
                // JAVA : D
}
// Dynamic binding
class Animal
{
public:
    virtual void Cry() { std::cout << "A Cry" << std::endl; }
};

class Dog : public Animal
{
public:
    virtual void Cry() override { std::cout << "D Cry" << std::endl; }
};

int main()
{
    Animal a;
    Dog d;

    Animal* p = &d;
    p->Cry();   // C++ : A
                // JAVA : D
}
반응형

반응형
  • 강한 결합(Tightly coupling)
    • 하나의 클래스가 다른 클래스를 사용할 때 서로의 이름을 직접 사용하는 것
    • 교체 불가능하고 확장성이 없음
class Camera
{
public:
    void take()
    {
        std::cout << "task picture" << std::endl;
    }
};

class HDCamera
{
public:
    void take()
    {
        std::cout << "task picture" << std::endl;
    }
};

class People
{
public:
    void useCamera(Camera* p) { p->take(); } // People이 Camera를 직접 접근
    void useCamera(HDCamera* p) { p->take(); } // People이 HDCamera를 직접 접근
};

int main()
{
    People p;
    Camera c1;
    p.useCamera(&c1);

    HDCamera c2;
    p.useCamera(&c2);
}

약한 결합(Loosely coupling)

  • 하나의 클래스가 다른 클래스를 사용할 때 인터페이스(추상 클래스)를 사용하는 것
  • 교체 가능하고, 확장성이 높음
// 사람과 카메라사이의 규칙 설계
// 모든 카메라는 ICamera 인터페이스를 구현 해야 함
class ICamera
{
public:
    virtual void take() = 0;
};

class People
{
public:
    void useCamera(ICamera* p) { p->take(); } // People이 공통 인터페이스(ICamera)로 접근
};

// 모든 카메라는 규칙을 지켜야 한다
class Camera : public ICamera
{
public:
    virtual void take() override
    {
        std::cout << "task picture1" << std::endl;
    }
};

class HDCamera : public ICamera
{
public:
    virtual void take() override
    {
        std::cout << "task picture2" << std::endl;
    }
};

int main()
{
    People p;
    Camera c1;
    p.useCamera(&c1);

    HDCamera c2;
    p.useCamera(&c2);
}
반응형

반응형

도형편집기로 배우는 객체지향 프로그래밍

  • 각 도형을 타입으로 만들면 편함
  • 모든 도형 공통의 기반 클래스가 있으면 각 도형을 묶어서 관리 할 수 있음
  • 모든 도형의 공통의 특징(Draw)는 반드시 기반 클래스(Shape)에도 있어야 함
  • 기반 클래스의 함수 중 파생 클래스가 재정의 하게 되는 모든 함수는 가상함수가 되어야 함

다형성(Polymorphism)

  • 동일한 코드 표현(함수 호출)이 상황에 따라 다르게 동작하는 것
  • OCP(Open Close Principle)
    • 기능 확장에는 열려(Open) 있고 코드 수정에는 닫혀(Close) 있어야 한다는 이론(Principle)
    • 다형성은 OCP를 만족할 수 있는 좋은 코드
#include <iostream>
#include <vector>

// 도형
class Shape
{
public:
    virtual void Draw()
    {
        std::cout << "Shape Draw" << std::endl;
    }
};

// 사각형
class Rect : public Shape
{
public:
    virtual void Draw() override
    {
        std::cout << "Rect Draw" << std::endl;
    }
};

// 원
class Circle : public Shape
{
public:
    virtual void Draw() override
    {
        std::cout << "Circle Draw" << std::endl;
    }
};

// 삼각형
class triangle : public Shape
{
public:
    virtual void Draw() override
    {
        std::cout << "Circle Draw" << std::endl;
    }
};

int main()
{
    std::vector<Shape*> v1; // 도형 저장 버퍼

    while (1)
    {
        int n;
        std::cin >> n;

        if (n == 1) v1.push_back(new Rect); // 사용자 입력이 1일때 사각형 추가
        else if (n == 2) v1.push_back(new Circle); // 사용자 입력이 2일때 원형 추가
        else if (n == 9) // 사용자 입력이 9일때 추가된 도형 전체 출력
        {
            for (int i = 0; i < v1.size(); i++)
                v1[i]->Draw(); // 다형성(Polymorphism) 특징
        }
    }
}
반응형

반응형

오버라이드

  • function override
    • 기반 클래스의 함수를 파생 클래스에서 재정의 할 수 있음
  • 기반 클래스의 포인터로 파생 클래스 객체를 가르킬때(Animal* p = &dog)
    • p->Cry() : C++은 Animal::Cry() 호출, java는 Dog::Cry()  호출
  • virtual function
    • 멤버 함수 호출시, 포인터 타입이 아닌 실제 객체에 따라 함수를 호출
    • 실행 시간에 포인터가 가리키는 메모리를 조사한 후 호출 하는것
// 기본 타입 예제
#include <iostream>
#include <vector>

class Animal
{
public:
    void Cry()
    {
        std::cout << "Animal Cry" << std::endl;
    }
};

class Dog : public Animal
{
public:
    // 함수 오버라이드(override)
    void Cry()
    {
        std::cout << "Dog Cry" << std::endl;
    }
};
int main()
{
    Animal a; a.Cry(); // Animal::Cry() 호출
    Dog d; d.Cry(); // Dog::Cry() 호출

    Animal* p = &d;
    p->Cry(); // Animal::Cry() 호출,  C++에서는 포인터 메모리 타입의 함수를 호출함
}

/////////////////////////////////////////////////////////////

// 가상 함수 변경 예저
#include <iostream>
#include <vector>

class Animal
{
public:
    // 가상 함수
    virtual void Cry()
    {
        std::cout << "Animal Cry" << std::endl;
    }
};

class Dog : public Animal
{
public:
    // 함수 오버라이드(override)
    virtual void Cry()
    {
        std::cout << "Dog Cry" << std::endl;
    }
};
int main()
{
    Animal a; a.Cry(); // Animal::Cry() 호출
    Dog d; d.Cry(); // Dog::Cry() 호출

    Animal* p = &d;
    p->Cry(); // Dog::Cry() 호출, 가상 함수이므로 객체에 따라 함수 호출
}

virtual function

  • 가상 함수 재정의(override)시에는 virtual 키워드는 붙여도 되고 붙이지 않아도 됨
  • 가상 함수 재정의시 실수를 막기 위해서 override 키워드를 사용(C++11)
  • 가상 함수를 선언과 구현으로 분리 할때는 선언부에만 virtual, override을 표기
#include <iostream>
#include <vector>

class Animal
{
public:
    virtual void Cry()
    {
        std::cout << "Animal Cry" << std::endl;
    }
};

class Dog : public Animal
{
public:
    // 함수 오버라이드(override)
    virtual void Cry() override // virtual, override를 제거해도 빌드 되지만 실수 방지용
    {
        std::cout << "Dog Cry" << std::endl;
    }
};
int main()
{
    Animal a; a.Cry();
    Dog d; d.Cry();

    Animal* p = &d;
    p->Cry();
}

 

virtual destructor

  • Upcasting사용시 기반 클래스의 소멸자는 반드시 가상 소멸자여야 함
class Base
{
public:
    Base() { std::cout << "Base 자원 할당" << std::endl; }
    virtual ~Base() { std::cout << "Base 자원 해지" << std::endl;  }
};

class Derived : public Base
{
public:
    Derived()
    {
        std::cout << "Derived 자원 할당" << std::endl;
    }
    virtual ~Derived()
    {
        std::cout << "Derived 자원 해지" << std::endl;
    }
};

int main()
{
    Derived* p = new Derived;
    delete p;
}
반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 추상 클래스(Abstract Class)  (0) 2019.05.12
C++ 상속 예제(Polymorphism)  (0) 2019.05.12
C++ 업캐스팅(Up Casting)  (0) 2019.05.12
C++ 상속(Inheritance)  (0) 2019.05.12
C++ STL 정리  (0) 2019.05.12

반응형

Upcasting #1

  • 기반 클래스의 포인터로 파생 클래스를 가리킬 수는 있지만 파생 클래스의 고유한 멤버에 접근은 불가
  • 파생 클래스의 고유 멤버에 접근 하려면 기반 클래스 포인터를 파생 클래스의 포인터 타입으로 캐스팅 해야함
#include <iostream>
#include <string>

class Animal
{
public:
    int age;
    std::string name;
};

class Dog : public Animal
{
public:
    int color;
    int getColor() const { return color; }
};

int main()
{
    Dog dog; // 객체의 포인터 크기, 32bit : 4byte, // 64bit : 64bit

    //int* p = &dog; // error, 서로다른 타입, reinterpret_cast
    Animal* p = &dog; // ok.. 기반 클래스가 -> 파생 클래스를 가르키는 방법
    p->age = 20; // 기반 클래스 멤버
    p->name = "AA"; // 기반 클래스 멤버
    p->color = 1; // 파생 클래스 멤버(error)
    p->getColor(); // 파생 클래스 멤버(error)
    static_cast<Dog*>(p)->color = 10; // 파생 클래스 멤버에 접근을 위해서는 static_cast 필요
}

Upcasting #2

  • 동종(동일한 기반 클래스를 사용하는 클래스)을 처리하는 함수를 만들 수 있음
#include <iostream>
#include <string>

class Animal
{
public:
    int age;
};

class Cat : public Animal {};
class Dog : public Animal {};

void HappyNewYear(Animal* p) // Upcasting이 없으면 Cat*, Dog*... 등의 각 동물별 함수 필요
{
    ++(p->age);
}

int main()
{
    Dog dog;
    HappyNewYear(&dog);

    Cat cat;
    HappyNewYear(&cat);
}
  • 동종을 보관하는 컨테이너를 만들 수 있음
#include <vector>

class Animal
{
public:
    int age;
};

class Cat : public Animal {};
class Dog : public Animal {};

int main()
{
    std::vector<Dog*> v1; // Dog만 보관
    std::vector<Cat*> v2; // Cat만 보관
    std::vector<Animal*> v3; // 모든 동물 보관
}

예제(Composite 패턴)

  • 윈도우 탐색기
    • Folder는 File, Folder를 함께 보관
      • File, Folder는 공통의 기반 클래스가 필요
        • folder : item
        • file : item
      • A, B를 함께 보관하고 싶다면 공통의 기반 클래스가 필요
  • 파워포인트
    • Component
      • Group : Component
      • Shape : Component
        • Rect : Shape
        • Circle : Shape
반응형

'프로그래밍 언어 > C++' 카테고리의 다른 글

C++ 상속 예제(Polymorphism)  (0) 2019.05.12
C++ 오버라이드, 가상함수(Override & Virtual Function)  (0) 2019.05.12
C++ 상속(Inheritance)  (0) 2019.05.12
C++ STL 정리  (0) 2019.05.12
C++ STL 정책(Policy Base)  (0) 2019.05.12

+ Recent posts