C++中实现虚函数需在基类方法前加virtual关键字,通过vtable和vptr实现运行时多态,确保基类指针调用派生类重写方法;同时应将基类析构函数声明为虚函数,以防止内存泄漏。

C++中实现虚函数,核心在于在基类的方法声明前加上
virtual
要让C++实现虚函数并发挥多态性,你需要遵循几个步骤,这其实很简单,但理解其背后的原理会让你的代码更健壮、更符合面向对象的设计思想。
首先,在你的基类中,你需要将那些你希望派生类能够重写,并且在通过基类指针或引用调用时能表现出多态行为的方法声明为
virtual
#include <iostream>
#include <vector>
#include <memory> // for std::unique_ptr
// 基类
class Animal {
public:
// 声明一个虚函数
virtual void speak() const {
std::cout << "Animal makes a sound." << std::endl;
}
// 虚析构函数,非常重要,我们稍后会提到
virtual ~Animal() {
std::cout << "Animal destructor called." << std::endl;
}
};
// 派生类 Dog
class Dog : public Animal {
public:
// 重写基类的虚函数
void speak() const override { // override 关键字是C++11引入的,用于明确指出这是对基类虚函数的重写
std::cout << "Dog barks: Woof! Woof!" << std::endl;
}
~Dog() {
std::cout << "Dog destructor called." << std::endl;
}
};
// 派生类 Cat
class Cat : public Animal {
public:
// 重写基类的虚函数
void speak() const override {
std::cout << "Cat meows: Meow~" << std::endl;
}
~Cat() {
std::cout << "Cat destructor called." << std::endl;
}
};
int main() {
// 使用基类指针指向派生类对象
Animal* myDog = new Dog();
Animal* myCat = new Cat();
Animal* genericAnimal = new Animal();
myDog->speak(); // 输出: Dog barks: Woof! Woof!
myCat->speak(); // 输出: Cat meows: Meow~
genericAnimal->speak(); // 输出: Animal makes a sound.
// 清理内存
delete myDog;
delete myCat;
delete genericAnimal;
std::cout << "--- Using std::vector and smart pointers ---" << std::endl;
std::vector<std::unique_ptr<Animal>> animals;
animals.push_back(std::make_unique<Dog>());
animals.push_back(std::make_unique<Cat>());
animals.push_back(std::make_unique<Animal>());
for (const auto& animalPtr : animals) {
animalPtr->speak();
}
// unique_ptr 会在超出作用域时自动调用析构函数,正确地释放内存。
// 如果Animal的析构函数不是虚的,这里会导致问题。
return 0;
}在这个例子中,
Animal
speak()
virtual
Dog
Cat
speak()
main
Animal*
speak()
Animal*
Dog
Cat
speak()
立即学习“C++免费学习笔记(深入)”;
这个问题其实触及了C++对象模型的一个核心。在C++中,默认情况下,函数的调用是静态绑定的,也就是在编译时就确定了要调用哪个函数。这意味着,如果你有一个基类指针指向一个派生类对象,然后你调用这个指针上的一个非虚函数,那么编译器会根据指针的静态类型(基类类型)来决定调用基类的函数,而不是派生类的函数。这在很多场景下是不可接受的,因为它违背了面向对象编程中“一个接口,多种实现”的原则。
想象一下,你有一个
Shape
draw()
Circle
Square
draw()
Shape*
draw()
Circle
Square
draw()
Shape*
Circle
Square
Shape
draw()
虚函数就是为了解决这个问题而生的。它告诉编译器:“嘿,这个函数在运行时可能需要根据对象的实际类型来决定调用哪个版本,别急着在编译时就定死。”通过引入虚函数,C++将函数的绑定从编译时(静态绑定)推迟到了运行时(动态绑定),从而实现了真正的运行时多态。这使得我们能够编写更加灵活、可扩展的代码,符合开放-封闭原则(对扩展开放,对修改封闭)。
要搞清楚虚函数的工作原理,就不得不提虚函数表(vtable)和虚指针(vpter)这两个幕后英雄。它们是C++实现动态绑定的核心机制。
说白了,当一个类中声明了虚函数,或者它继承了一个带有虚函数的基类时,编译器就会为这个类生成一个虚函数表(vtable)。这个vtable本质上是一个函数指针数组,数组的每个元素都指向该类中一个虚函数的实际实现地址。如果派生类重写了某个虚函数,那么vtable中对应的条目就会指向派生类的实现;如果没有重写,它就会指向基类的实现。
与此同时,这个类的每一个对象都会在它的内存布局中多出一个隐藏的虚指针(vpter)。这个vpter是一个指向该类vtable的指针。这个指针通常是对象内存布局的第一个成员(或者某个固定偏移量处)。
现在,我们来看它们是如何协同工作的:
Dog
Dog
Animal* myDog = new Dog();
myDog->speak();
myDog
speak()
speak()
所以,
myDog->speak()
(*(myDog->vpter[index_of_speak]))(myDog)
在C++的多态设计中,纯虚函数和抽象类扮演着“定义接口”和“强制实现”的角色,它们是更高级别的多态抽象。
纯虚函数,顾名思义,它是一个没有具体实现的虚函数。你在声明一个虚函数时,如果在其后面加上
= 0
class Shape {
public:
// 纯虚函数,表示所有形状都“应该”有绘制行为,但基类本身无法提供具体实现
virtual void draw() const = 0;
virtual ~Shape() {} // 虚析构函数仍然是推荐的
};一个类中只要包含一个或多个纯虚函数,那么这个类就自动成为了抽象类。抽象类有以下几个关键特性:
Shape s;
Shape* s = new Shape();
Shape
draw()
Shape
Circle
Square
draw()
纯虚函数和抽象类在多态设计中的作用非常明确:它们允许你设计一个顶层概念,这个概念本身可能没有一个通用的、有意义的实现(比如“形状”的
draw()
虚析构函数在C++多态中是一个经常被忽视但极其重要的概念,尤其是在涉及动态内存管理时。它的重要性体现在防止内存泄漏和未定义行为上。
问题背景: 设想一下,你有一个基类
Base
Derived
Derived
class Base {
public:
Base() { std::cout << "Base constructor." << std::endl; }
~Base() { std::cout << "Base destructor." << std::endl; } // 非虚析构函数
};
class Derived : public Base {
private:
int* data;
public:
Derived() : data(new int[10]) { std::cout << "Derived constructor." << std::endl; }
~Derived() {
std::cout << "Derived destructor." << std::endl;
delete[] data; // 释放派生类分配的资源
}
};
int main() {
Base* ptr = new Derived(); // 基类指针指向派生类对象
delete ptr; // 预期调用Derived的析构函数,然后是Base的析构函数
return 0;
}在上述代码中,
delete ptr;
Base
virtual
delete ptr;
ptr
Base*
Base
Derived
data
虚析构函数的解决方案: 要解决这个问题,只需要将基类的析构函数声明为
virtual
class Base {
public:
Base() { std::cout << "Base constructor." << std::endl; }
virtual ~Base() { std::cout << "Base destructor." << std::endl; } // 虚析构函数
};
class Derived : public Base {
private:
int* data;
public:
Derived() : data(new int[10]) { std::cout << "Derived constructor." << std::endl; }
~Derived() {
std::cout << "Derived destructor." << std::endl;
delete[] data;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // 现在会正确调用Derived的析构函数,然后是Base的析构函数
return 0;
}现在,当
delete ptr;
Base
Derived
Derived
Base
原理: 虚析构函数的原理与普通虚函数完全相同,都是基于vtable和vpter机制。当基类析构函数被声明为
virtual
何时需要虚析构函数? 一个简单的规则是:如果一个类有任何虚函数,那么它的析构函数就应该声明为虚函数。 这样可以确保在使用多态时,通过基类指针删除派生类对象时能够正确地释放所有资源。如果一个类不打算被继承,或者它没有虚函数,那么它的析构函数通常不需要是虚的。
以上就是c++++如何实现虚函数_c++多态核心之虚函数工作原理的详细内容,更多请关注php中文网其它相关文章!
c++怎么学习?c++怎么入门?c++在哪学?c++怎么学才快?不用担心,这里为大家提供了c++速学教程(入门到精通),有需要的小伙伴保存下载就能学习啦!
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号