[2026] C++ Virtual Functions: Polymorphism, override, and Pure Virtual

[2026] C++ Virtual Functions: Polymorphism, override, and Pure Virtual

이 글의 핵심

Virtual functions in C++: dynamic dispatch, virtual vs non-virtual, pure virtual and abstract classes, virtual destructors, vtables, and slicing pitfalls.

What are virtual functions?

This article explains how virtual functions enable C++ polymorphism, why the right override runs at runtime, and how to use override, pure virtual functions, and virtual destructors in real code. Functions whose dynamic type determines which override runs at runtime. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Animal {
public:
    virtual void speak() {
        cout << "Animal sound" << endl;
    }
};
class Dog : public Animal {
public:
    void speak() override {
        cout << "Woof!" << endl;
    }
};
class Cat : public Animal {
public:
    void speak() override {
        cout << "Meow!" << endl;
    }
};
int main() {
    Animal* animal1 = new Dog();
    Animal* animal2 = new Cat();
    
    animal1->speak();  // Woof!
    animal2->speak();  // Meow!
    
    delete animal1;
    delete animal2;
}

virtual vs non-virtual

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
public:
    void nonVirtual() {
        cout << "Base::nonVirtual" << endl;
    }
    
    virtual void virtualFunc() {
        cout << "Base::virtualFunc" << endl;
    }
};
class Derived : public Base {
public:
    void nonVirtual() {
        cout << "Derived::nonVirtual" << endl;
    }
    
    void virtualFunc() override {
        cout << "Derived::virtualFunc" << endl;
    }
};
int main() {
    Base* ptr = new Derived();
    
    ptr->nonVirtual();   // Base::nonVirtual (static binding)
    ptr->virtualFunc();  // Derived::virtualFunc (dynamic binding)
    
    delete ptr;
}

Pure virtual functions

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Shape {
public:
    virtual double area() const = 0;
    virtual void draw() const = 0;
    
    virtual ~Shape() = default;
};
class Circle : public Shape {
private:
    double radius;
    
public:
    Circle(double r) : radius(r) {}
    
    double area() const override {
        return 3.14159 * radius * radius;
    }
    
    void draw() const override {
        cout << "Drawing Circle" << endl;
    }
};
class Rectangle : public Shape {
private:
    double width, height;
    
public:
    Rectangle(double w, double h) : width(w), height(h) {}
    
    double area() const override {
        return width * height;
    }
    
    void draw() const override {
        cout << "Drawing Rectangle" << endl;
    }
};
int main() {
    // Shape shape;  // error: abstract class
    
    vector<unique_ptr<Shape>> shapes;
    shapes.push_back(make_unique<Circle>(5.0));
    shapes.push_back(make_unique<Rectangle>(4.0, 6.0));
    
    for (const auto& shape : shapes) {
        shape->draw();
        cout << "Area: " << shape->area() << endl;
    }
}

Practical examples

Example 1: File-system tree

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 반복문으로 데이터를 처리합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class FileSystemNode {
protected:
    string name;
    
public:
    FileSystemNode(const string& n) : name(n) {}
    virtual ~FileSystemNode() = default;
    
    virtual void print(int indent = 0) const = 0;
    virtual size_t getSize() const = 0;
};
class File : public FileSystemNode {
private:
    size_t size;
    
public:
    File(const string& n, size_t s) : FileSystemNode(n), size(s) {}
    
    void print(int indent = 0) const override {
        cout << string(indent, ' ') << "- " << name 
             << " (" << size << " bytes)" << endl;
    }
    
    size_t getSize() const override {
        return size;
    }
};
class Directory : public FileSystemNode {
private:
    vector<unique_ptr<FileSystemNode>> children;
    
public:
    Directory(const string& n) : FileSystemNode(n) {}
    
    void add(unique_ptr<FileSystemNode> node) {
        children.push_back(move(node));
    }
    
    void print(int indent = 0) const override {
        cout << string(indent, ' ') << "+ " << name << "/" << endl;
        for (const auto& child : children) {
            child->print(indent + 2);
        }
    }
    
    size_t getSize() const override {
        size_t total = 0;
        for (const auto& child : children) {
            total += child->getSize();
        }
        return total;
    }
};
int main() {
    auto root = make_unique<Directory>("root");
    
    auto docs = make_unique<Directory>("docs");
    docs->add(make_unique<File>("readme.txt", 1024));
    docs->add(make_unique<File>("guide.pdf", 5120));
    
    root->add(move(docs));
    root->add(make_unique<File>("main.cpp", 2048));
    
    root->print();
    cout << "Total size: " << root->getSize() << " bytes" << endl;
}

Example 2: Strategy pattern

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class PaymentStrategy {
public:
    virtual ~PaymentStrategy() = default;
    virtual void pay(double amount) = 0;
};
class CreditCardPayment : public PaymentStrategy {
private:
    string cardNumber;
    
public:
    CreditCardPayment(const string& card) : cardNumber(card) {}
    
    void pay(double amount) override {
        cout << "Card " << cardNumber << " pays " 
             << amount << " KRW" << endl;
    }
};
class PayPalPayment : public PaymentStrategy {
private:
    string email;
    
public:
    PayPalPayment(const string& e) : email(e) {}
    
    void pay(double amount) override {
        cout << "PayPal " << email << " pays " 
             << amount << " KRW" << endl;
    }
};
class ShoppingCart {
private:
    unique_ptr<PaymentStrategy> paymentStrategy;
    double total = 0;
    
public:
    void setPaymentStrategy(unique_ptr<PaymentStrategy> strategy) {
        paymentStrategy = move(strategy);
    }
    
    void addItem(double price) {
        total += price;
    }
    
    void checkout() {
        if (paymentStrategy) {
            paymentStrategy->pay(total);
            total = 0;
        }
    }
};
int main() {
    ShoppingCart cart;
    cart.addItem(10000);
    cart.addItem(20000);
    
    cart.setPaymentStrategy(make_unique<CreditCardPayment>("1234-5678"));
    cart.checkout();
    
    cart.addItem(15000);
    cart.setPaymentStrategy(make_unique<PayPalPayment>("user@example.com"));
    cart.checkout();
}

Example 3: Logger

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Logger {
public:
    virtual ~Logger() = default;
    virtual void log(const string& message) = 0;
};
class ConsoleLogger : public Logger {
public:
    void log(const string& message) override {
        cout << "[Console] " << message << endl;
    }
};
class FileLogger : public Logger {
private:
    string filename;
    
public:
    FileLogger(const string& file) : filename(file) {}
    
    void log(const string& message) override {
        ofstream ofs(filename, ios::app);
        ofs << "[File] " << message << endl;
    }
};
class Application {
private:
    unique_ptr<Logger> logger;
    
public:
    void setLogger(unique_ptr<Logger> l) {
        logger = move(l);
    }
    
    void run() {
        if (logger) {
            logger->log("Application started");
            logger->log("Application finished");
        }
    }
};
int main() {
    Application app;
    
    app.setLogger(make_unique<ConsoleLogger>());
    app.run();
    
    app.setLogger(make_unique<FileLogger>("app.log"));
    app.run();
}

vtable and vptr

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Base {
public:
    virtual void func1() {}
    virtual void func2() {}
};
// Conceptually:
// Base object = [vptr] + [members...]
// vptr -> vtable (addresses of func1, func2)

Virtual destructor

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
public:
    virtual ~Base() {
        cout << "~Base()" << endl;
    }
};
class Derived : public Base {
private:
    int* data;
    
public:
    Derived() : data(new int[100]) {}
    
    ~Derived() {
        cout << "~Derived()" << endl;
        delete[] data;
    }
};
int main() {
    Base* ptr = new Derived();
    delete ptr;  // ~Derived() then ~Base()
}

Common pitfalls

Pitfall 1: Non-virtual destructor

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Bad
class Base {
public:
    ~Base() {
        cout << "~Base()" << endl;
    }
};
class Derived : public Base {
private:
    int* data;
    
public:
    Derived() : data(new int[100]) {}
    
    ~Derived() {
        cout << "~Derived()" << endl;
        delete[] data;  // not called through Base*!
    }
};
Base* ptr = new Derived();
delete ptr;  // leak
// Good: virtual destructor
class Base {
public:
    virtual ~Base() {
        cout << "~Base()" << endl;
    }
};

Pitfall 2: Missing override

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
public:
    virtual void func() {}
};
// Typo creates a new function
class Derived : public Base {
public:
    void fucn() {}  // typo
};
// With override: compile error
class Derived : public Base {
public:
    void fucn() override {}  // error
};

Pitfall 3: Slicing

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
public:
    virtual void func() {
        cout << "Base" << endl;
    }
};
class Derived : public Base {
public:
    void func() override {
        cout << "Derived" << endl;
    }
};
Derived d;
Base b = d;  // slices
b.func();    // Base
Base* ptr = &d;
ptr->func(); // Derived

FAQ

Q1: When to use virtual functions?

A:

  • Runtime polymorphism
  • Interface-style extension
  • Abstract base classes

Q2: Performance overhead?

A: Small—vtable indirection. Usually negligible vs flexibility.

Q3: Pure virtual functions?

A: = 0 makes the class abstract; derived types must implement.

Q4: override keyword?

A: C++11—documents intent and catches signature mistakes.

Q5: Virtual destructor required?

A: Whenever you delete derived objects through a base pointer.

Q6: Learning resources?

A:

  • Effective C++
  • cppreference.com
  • C++ Primer

Practical tips

Debugging

  • Warnings first

Performance

  • Profile

Code review

  • Conventions

Practical checklist

Before coding

  • Right approach?
  • Maintainable?
  • Performance?

While coding

  • Warnings?
  • Edge cases?
  • Errors?

At review

  • Intent?
  • Tests?
  • Docs?

Keywords

C++, virtual function, polymorphism, override, OOP

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3