Language/C++

📌 Chapter 09 - 가상

e-cko 2025. 9. 2. 14:27
반응형
반응형

1. 함수와 테이블

1) 멤버 변수와 멤버 함수의 차이

  • 멤버 변수는 객체마다 별도로 소유된다.
  • 멤버 함수는 클래스가 정의한 하나의 내용을 모든 객체가 공유한다.
  • 객체마다 같은 동작을 하게 하기 위해 함수는 공유 구조를 갖는다.

2) 가상 함수(Virtual Function)

  • 가상 함수는 상속 관계에서 오버라이딩(재정의)을 통해 다형성을 구현하는 함수이다.
  • virtual 키워드를 사용하여 선언한다.
  • 객체 포인터나 참조를 통해 호출할 경우, 실제 객체 타입에 맞는 함수가 실행된다.

3) 가상 함수 테이블(Virtual Table, V-Table)

  • 클래스가 가상 함수를 하나라도 포함하면, 컴파일러는 가상 함수 테이블(V-Table)을 생성한다.
  • V-Table은 일종의 내부 구조로, 가상 함수 호출 시 어떤 함수가 실행되어야 하는지를 결정한다.
  • 직접 접근할 수는 없지만, 실행 흐름에는 필수적으로 사용된다.

V-Table의 개념적 구조

클래스: Animal

----------------------

가상 함수 목록:

- speak() → 0x001F3B20

- move()  → 0x002A1744

 

클래스: Dog (Animal 상속)

----------------------

가상 함수 목록:

- speak() → 0x003D1020  // 오버라이딩된 주소

- move()  → 0x002A1744  // 기본 상속된 주소

· Key는 함수 식별자, Value는 해당 함수의 메모리 주소이다.
·
객체 생성과 관계없이, 클래스 수준에서 V-Table메모리에 먼저 할당된다.
·
함수 호출 시, 객체 내부의 **vptr(가상 함수 테이블 포인터)**를 통해 테이블을 참조한다.
·
이 과정을 거치므로, ⚠️ C++ C에 비해 실행 속도가 조금 느릴 수 있다.


2. 다중 상속과 가상 상속

1) 다중 상속(Multiple Inheritance)

  • 다중 상속이란, 하나의 클래스가 두 개 이상의 기초 클래스(Base Class)로부터 상속받는 구조이다.
  • 유연한 구조를 설계할 수 있지만, ⚠️ 구조가 복잡해지고 문제 발생 가능성이 높다.

2) 다중 상속의 문제점 - 모호성(Ambiguity)

  • 여러 기초 클래스에 동일한 이름의 멤버가 존재할 경우, 호출 시 어떤 클래스의 멤버를 참조할지 불분명해진다.
  • 이 경우, 기초클래스명::멤버명 형식으로 명시적으로 지정해야 한다.

예시

class A { int value = 1; };

class B : public A {};

class C : public A {};

class D : public B, public C {};

 

D d;

d.value; // ⚠️ 오류 발생: A::value B C에 모두 존재하여 모호함

// 해결 방법: d.B::value; // 또는 d.C::value;

3) 가상 상속(Virtual Inheritance)

  • 가상 상속은 중복 상속 문제를 해결하기 위한 구조이다.
  • virtual 키워드를 사용하여 기초 클래스를 가상으로 상속받는다.
  • 유도 클래스가 여러 중간 클래스를 통해 같은 기초 클래스를 상속받더라도, ⚠️ 하나의 기초 클래스만 공유되도록 처리된다.

가상 상속 전/후 비교 예시

📌 [가상 상속을 사용하지 않은 경우]

class A { int value; };

class B : public A {};

class C : public A {};

class D : public B, public C {};

 

// D 객체는 A 클래스를 두 번 상속받게 되어

// D 안에는 A::value가 두 개 존재함⚠️ 모호성 발생

📌 [가상 상속을 사용한 경우]

class A { int value; };

class B : virtual public A {};

class C : virtual public A {};

class D : public B, public C {};

 

// D 객체는 A를 단 한 번만 상속받음 모호성 제거


마무리 정리

1️⃣ 멤버 변수는 객체가 따로 가지고, 멤버 함수는 객체들이 공유한다.
2️⃣
가상 함수는 virtual 키워드를 사용하며, 다형성을 구현하는 사용된다.
3️⃣
가상 함수가 포함된 클래스는 **컴파일 V-Table(가상 함수 테이블)** 생성한다.
4️⃣ V-Table
함수 이름(Key) 함수 주소(Value) 구성되며, 간접 호출 구조를 가진다.
5️⃣
다중 상속은 하나의 클래스가 여러 기초 클래스를 상속받는 구조이며, ⚠️ 모호성 문제를 유발할 있다.
6️⃣
모호성은 기초클래스명::멤버명으로 명시하여 해결할 있다.
7️⃣ **
가상 상속(Virtual Inheritance)** virtual 키워드를 사용하여 중복 상속 문제를 방지한다.
8️⃣
가상 상속을 사용하면 여러 경로로 상속받은 기초 클래스의 인스턴스를 하나만 공유하게 된다.
9️⃣ C++
가상 함수 호출 테이블을 간접 참조하므로, C보다 속도가 느릴 있다.
🔟
다중 상속과 가상 상속은 유연한 구조를 제공하지만, ⚠️ 설계 주의가 필요하다.


💻 예제 코드 - 가상 함수와 가상 상속

#include <iostream>

using namespace std;

 

// 기초 클래스

class Animal {

public:

    virtual void speak() { // 가상 함수 선언

        cout << "Animal sound\n";

    }

};

 

// 중간 클래스 1 - 가상 상속

class Dog : virtual public Animal {

public:

    void speak() override { // 오버라이딩

        cout << "Woof!\n";

    }

};

 

// 중간 클래스 2 - 가상 상속

class Cat : virtual public Animal {

public:

    void speak() override {

        cout << "Meow!\n";

    }

};

 

// 최종 클래스 - 다중 상속

class Pet : public Dog, public Cat {

public:

    // speak() 다시 정의하지 않으면 ambiguous 발생 가능

    void speak() override {

        cout << "Mixed pet sound\n";

    }

};

 

int main() {

    Pet myPet;        // Pet 객체 생성

    Animal* ptr = &myPet; // 기초 클래스 포인터로 참조

    ptr->speak();     // 가상 함수 호출: 실제 객체 타입(Pet) speak() 실행됨

 

    // 모호성 없는 직접 호출

    myPet.Dog::speak(); // "Woof!" 출력

    myPet.Cat::speak(); // "Meow!" 출력

 

    return 0;

}

🧾 실행 결과

Mixed pet sound

Woof!

Meow!

🔎 코드 설명

  • Animal 가상 함수 speak() 가진 기초 클래스이다.
  • Dog, Cat Animal 가상 상속하여 Pet 상속 Animal 하나만 공유한다.
  • Pet에서 speak() 오버라이딩하지 않으면, 어떤 speak() 호출할지 모호해진다.
  • 가상 함수 호출 , 실제 객체 타입에 따라 Pet speak() 호출된다.
  • 경로의 speak() 기초클래스명::함수명() 형식으로 명확히 호출할 있다.
반응형