C++ Virtual Functions: Polymorphism, override, and Pure Virtual
이 글의 핵심
Practical guide to C++ virtual functions: runtime polymorphism, override safety, abstract interfaces, and patterns like strategy and logging.
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.
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
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
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
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
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>("[email protected]"));
cart.checkout();
}
Example 3: Logger
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
class Base {
public:
virtual void func1() {}
virtual void func2() {}
};
// Conceptually:
// Base object = [vptr] + [members...]
// vptr -> vtable (addresses of func1, func2)
Virtual destructor
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
// 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
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
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
Related posts (internal links)
- Inheritance and polymorphism
- Object slicing
- VTable
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
Related posts
- Inheritance
- Virtual destructor
- Object slicing
- Classes and objects
- CRTP pattern