[2026] C++ vtable 에러 | undefined reference to vtable 링커 에러 해결
이 글의 핵심
C++ vtable 에러의 C++, vtable, undefined, 들어가며: undefined reference to vtable for MyClass를 실전 예제와 함께 상세히 설명합니다.
들어가며: “undefined reference to vtable for MyClass"
"가상 함수를 선언했더니 링커 에러가 나요”
C++에서 가상 함수를 선언만 하고 정의하지 않으면 vtable 링커 에러가 발생합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ vtable 에러
class Base {
public:
virtual void foo(); // 선언만
virtual ~Base(); // 선언만
};
// 링크 에러:
// undefined reference to `vtable for Base'
이 글에서 다루는 것:
- vtable이란?
- vtable 에러 원인
- 해결 방법
- 순수 가상 함수
실무에서 마주한 현실
개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.
목차
1. vtable이란?
vtable (Virtual Table)
vtable은 가상 함수 테이블로, 런타임에 어떤 함수를 호출할지 결정하는 테이블입니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class Base {
public:
virtual void foo() {
std::cout << "Base::foo\n";
}
virtual void bar() {
std::cout << "Base::bar\n";
}
};
class Derived : public Base {
public:
void foo() override {
std::cout << "Derived::foo\n";
}
};
// vtable 구조 (개념적)
// Base vtable:
// [0] -> Base::foo
// [1] -> Base::bar
//
// Derived vtable:
// [0] -> Derived::foo
// [1] -> Base::bar
vtable 생성 시점
컴파일러는 첫 번째 비인라인 가상 함수가 정의된 번역 단위에 vtable을 생성합니다.
2. vtable 에러 원인
원인 1: 가상 함수 정의 누락
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 정의 없음
// base.h
class Base {
public:
virtual void foo(); // 선언만
virtual ~Base(); // 선언만
};
// 링크 에러: undefined reference to `vtable for Base'
원인 2: 소멸자 정의 누락
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ 소멸자 정의 없음
class Base {
public:
virtual ~Base(); // 선언만
virtual void foo() {
std::cout << "foo\n";
}
};
// 링크 에러: undefined reference to `vtable for Base'
원인 3: 순수 가상 함수 구현
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ❌ 순수 가상 함수를 일반 함수처럼 선언
class Base {
public:
virtual void foo(); // = 0 없음
};
// Base를 인스턴스화하려고 하면 링크 에러
3. 해결 방법
해결책 1: .cpp에 정의 추가
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ base.h
class Base {
public:
virtual void foo();
virtual ~Base();
};
// ✅ base.cpp
void Base::foo() {
std::cout << "Base::foo\n";
}
Base::~Base() {
std::cout << "~Base\n";
}
해결책 2: 헤더에 인라인 정의
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ base.h
class Base {
public:
virtual void foo() {
std::cout << "Base::foo\n";
}
virtual ~Base() {
std::cout << "~Base\n";
}
};
해결책 3: = default 사용
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ base.h
class Base {
public:
virtual void foo() {
std::cout << "Base::foo\n";
}
virtual ~Base() = default; // 기본 구현
};
4. 순수 가상 함수
순수 가상 함수는 = 0
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 순수 가상 함수
class Base {
public:
virtual void foo() = 0; // 순수 가상
virtual ~Base() = default;
};
class Derived : public Base {
public:
void foo() override {
std::cout << "Derived::foo\n";
}
};
// Base b; // 컴파일 에러: 추상 클래스
Derived d; // OK
순수 가상 소멸자는 정의 필요
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 순수 가상 소멸자
class Base {
public:
virtual ~Base() = 0; // 순수 가상
};
// 정의 필수!
Base::~Base() {
std::cout << "~Base\n";
}
class Derived : public Base {
public:
~Derived() override {
std::cout << "~Derived\n";
}
};
실전 예시
예시 1: 인터페이스 클래스
아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 인터페이스
class ILogger {
public:
virtual void log(const std::string& msg) = 0;
virtual ~ILogger() = default;
};
class ConsoleLogger : public ILogger {
public:
void log(const std::string& msg) override {
std::cout << msg << '\n';
}
};
예시 2: 추상 베이스 클래스
다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ✅ 추상 클래스
class Shape {
public:
virtual double area() const = 0;
virtual double perimeter() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
double radius_;
public:
Circle(double r) : radius_(r) {}
double area() const override {
return 3.14159 * radius_ * radius_;
}
double perimeter() const override {
return 2 * 3.14159 * radius_;
}
};
정리
vtable 에러 해결
| 원인 | 해결책 |
|---|---|
| 가상 함수 정의 없음 | .cpp에 정의 추가 |
| 소멸자 정의 없음 | = default 사용 |
| 순수 가상 표시 없음 | = 0 추가 |
| 순수 가상 소멸자 | 정의 제공 |
핵심 규칙
- 모든 가상 함수에 정의 제공 (순수 가상 제외)
- 순수 가상 함수는 = 0
- 순수 가상 소멸자도 정의 필요
- = default 활용
체크리스트
- 모든 가상 함수에 정의가 있는가?
- 가상 소멸자에 정의가 있는가?
- 순수 가상 함수는 = 0으로 표시했는가?
- 순수 가상 소멸자에 정의를 제공했는가?
같이 보면 좋은 글 (내부 링크)
이 주제와 연결되는 다른 글입니다.
- C++ 가상 함수 | virtual function 가이드
- C++ 가상 소멸자 | 메모리 누수 방지
- C++ 다형성 | Polymorphism 가이드
- C++ 링커 에러 | multiple definition 해결
마치며
vtable 에러는 가상 함수 정의 누락으로 발생합니다. 핵심 원칙:
- 모든 가상 함수에 정의 제공
- 순수 가상 함수는 = 0
- = default 활용 가상 함수를 선언했다면 반드시 정의를 제공하세요. 다음 단계: vtable을 이해했다면, C++ 가상 함수 가이드에서 더 깊이 배워보세요.