Skip to main content

[C#] virtual 메서드




virtual table #

가상 테이블
Image Source

  • 한 클래스가 해당 클래스 or 상위 클래스에서 virtual 메서드를 갖는다면,
    • 해당 클래스의 Method Table 메타데이터 내에 Virtual Table을 갖게 된다.
  • C#은 모든 클래스가 System.Object 의 상속을 받으므로 모든 클래스가 Virtual Table을 갖다고 볼 수 있겠다.
    • 4개의 가상메서드(ToString(), Equals(), GetHashCode(), Finalize())를 자신의 Virtual Table안에 갖게 된다.
  • 해당 클래스의 메서드는 virtual이 아니어도 모두 데이블에 추가된다.



override : virtual 메서드 동작원리 #

  • 부모 클래스의 virtual 메서드를 자식 클래스에서 override하면
    • (1) 자식클래스의 Virtual Table에 있는 부모 클래스의 가상 메서드 슬롯에, 자식 클래스의 override 메서드의 포인터를 집어 넣는다.
    • (2) 그리고 별도의 자식 클래스 메서드 슬롯을 새로 만들지 않는다.
  • 그래서 누군가 부모 클래스 타입으로 메서드를 요구하면
    • 부모 클래스의 가상 메서드 슬롯에 (이미 overriding되어서 넣어진) 가상 메서드 포인터를 리턴하는 것이고,
  • 자식 클래스 타입으로 메서드를 요구하면
    • 자식 클래스 메서드 슬롯에는 해당 메서드가 없으니 부모 클래스의 가상 메서드를 리턴하는데, 또 다시 그곳에는 overriding되어서 넣어진 가상 메서드 포인터가 있어서 그것을 리턴하는 것이다.



new : 메서드 hiding 동작원리 #

  • (new 키워드가 없어도 동일한 효과를 낸다.)
  • 자식 클래스에서 override를 쓰지 않고 부모 클래스와 동일한 메서드명을 사용하면, 자식 클래스에서는 부모 클래스의 메서드를 더이상 사용할 수 없게 된다는 것이다.
  • 이때는 부모 클래스의 가상 메서드 슬롯에 override 되지 않고, 자식 클래스의 메서드 슬롯에 해당 메서드가 새로 추가된다.
  • 그래서 부모 클래스 타입으로 호출하면
    • 부모 클래스의 가상 메서드 슬롯에 그래도 있는 부모 클래스의 메서드를 호출하고,
  • 자식 클래스 타입으로 호출하면
    • 자식 클래스의 메서드 슬롯에 있는 자식 클래스의 메서드를 호출한다.



생성자와 virtual 메서드 #

  • virtual 메서드를 생성자에서 부르면 어떻게 될까?
public class Mother
{
    public Mother()
    {
        DoSomething();
    }

    public virtual void DoSomething()
    {
        Console.Write("Mother ");
    }
}

public class Child : Mother
{
    public Child()
    {
        DoSomething();
    }

    public override void DoSomething()
    {
        Console.Write("Child ");
    }
}

class Main
{
    public void Run()
    {
        Mother m = new Mother(); // Mother
        Child c = new Child();   // Child Child
        Mother mc = new Child(); // Child Child
    }
}

  • 자식 클래스의 생성자 내부 코드가 먼저일까, 부모 클래스의 생성자 호출이 먼저일까?
    • 결론: 생성자에서는 가상함수를 호출하면 안 된다!
class Base
{
    // (4) 부모 클래스의 생성자 호출
    protected Base()
    {
        // (5) 가상 함수 호출
        VFunc();
        // 무엇이 출력될까? 정답은 Set by initializer!
        // 만약 VFunc()가 abstract 메서드라면 -> 똑같이 Set by initializer!
    }

    protected virtual void VFunc()
    {
        Console.WriteLine("VFunc in Base");
    }
}

class Derived : Base
{
    // (3) Derived의 멤버 초기화
    readonly string msg = "Set by initializer";

    // (2) Derived 생성자 호출
    public Derived(string msg)
    {
        // (마지막) 생성이 완료된 후에 생성자의 스코프 안으로 진입
        this.msg = msg;
    }

    // (6) 가상 함수 실행
    protected override void VFunc()
    {
        Console.WriteLine(msg);
    }
}

class Main
{
    public void Run()
    {
        // (1) Derived 객체 생성 
        var d = new Derived("Constructed in main");
    }
}

  • 혹시 C++은 어떨까?
#include <iostream>

class Base
{
protected:
    // (3) 부모 클래스의 생성자 호출
    Base()
    {
        // (4) 가상 함수 호출
        VFunc(); 
        // 무엇이 출력될까? 정답은 VFunc in Base!
        // 만약 VFunc()가 순수 가상 함수라면 -> 컴파일 에러가 발생한다!
    }

    // (5) 가상 함수 실행
    virtual void VFunc()
    {
        std::cout << "VFunc in Base" << std::endl;
    }
};

class Derived : public Base
{
private:
    // (6) Derived의 멤버 초기화
    std::string msg = "Set by initializer";

public:
    // (2) Derived 생성자 호출
    Derived(const std::string& msg)
    {
        // (마지막) 생성이 완료된 후에 생성자의 스코프 안으로 진입
        this->msg = msg;
    }

protected:
    virtual void VFunc() override
    {
        std::cout << msg << std::endl;
    }
};

int main()
{
      // (1) Derived 객체 생성 
      auto* d = new Derived("Constructed in main"); 
}



References #