Administrator
发布于 2026-01-09 / 4 阅读
0
0

关于虚函数几个容易误解的点

为什么构造函数不能是虚函数?
  1. 虚函数的调用依赖于虚函数表(vtable),而虚函数表是在构造函数中初始化的。在构造函数执行之前,对象还没有完全构建,虚函数表还没有建立,因此无法调用虚函数构造函数。
  2. 从语义上讲,虚函数是用来实现多态的,即在运行时根据对象的实际类型来调用相应的函数。但是,在对象构造期间,对象的具体类型是已知的(由当前正在执行的构造函数决定),因此不需要虚函数机制。实际上,在构造函数中调用虚函数也不会实现多态,而是调用当前类的版本。
  3. 构造函数的职责是初始化对象,而虚函数的行为是在对象已经构造完成后才有的多态行为。因此,将构造函数声明为虚函数没有意义。
为什么析构函数可以是虚函数?
  1. 析构函数可以是虚函数,而且当基类的析构函数是虚函数时,通过基类指针删除派生类对象时,会调用派生类的析构函数,从而正确释放资源。
  2. 虚析构函数确保在删除基类指针时,根据实际指向的对象类型调用相应的析构函数,实现多态。
  3. 如果基类的析构函数不是虚函数,那么通过基类指针删除派生类对象时,只会调用基类的析构函数,导致派生类的资源没有释放,造成内存泄漏。
class Base {
public:
    Base() { cout << "Base constructor" << endl; }
    virtual ~Base() { cout << "Base destructor" << endl; }  // 虚析构函数
};

class Derived : public Base {
public:
    Derived() { cout << "Derived constructor" << endl; }
    ~Derived() { cout << "Derived destructor" << endl; }
};

int main() {
    Base* p = new Derived();
    delete p;  // 正确调用派生类和基类的析构函数
    /*
    输出:
    Base constructor
    Derived constructor
    Derived destructor
    Base destructor
    */
    return 0;
}

在C++中,当在构造函数或析构函数中调用虚函数时,调用的版本是当前类

即(构造函数或析构函数所在类)中定义的版本,而不是派生类中重写的版本。这是因为在构造基类部分时,派生类部分还没有被构造,因此不能调用派生类的虚函数。同样,在析构函数中,派生类部分已经被销毁,所以也不能调用派生类的虚函数。

#include <iostream>
using namespace std;
 
class Base {
public:
    Base() {
        std::cout << "Base constructor\n";
        this->virtualFunction();  // 调用 Base::virtualFunction()
    }
    virtual void virtualFunction() {
        std::cout << "Base virtual function\n";
    }
    virtual ~Base() {
        std::cout << "Base destructor\n";
        this->virtualFunction();  // 调用 Base::virtualFunction()
    }
};

class Derived : public Base {
public:
    Derived() {
        std::cout << "Derived constructor\n";
    }
    virtual void virtualFunction() override {
        std::cout << "Derived virtual function\n";
    }
    ~Derived() {
        std::cout << "Derived destructor\n";
    }
};

int main() {
    Derived d;  
    // 都对象构造时:
    // Base constructor
    // Base virtual function  (不是 Derived 的版本!)
    // Derived constructor
  
    // 对象销毁时:
    // Derived destructor (先调用派生类析构函数)
    // Base destructor     (再调用基类析构函数)
    // Base virtual function  (不是 Derived 的版本!)
}
在C++中,当使用new创建一个派生类对象时,会依次调用基类和派生类的构造函数具体调用过程如下:
  1. 按继承顺序调用基类构造函数(如果没有显式调用基类构造函数,则调用默认构造函数)。
  2. 按声明顺序调用成员对象的构造函数(如果没有显式调用成员对象的构造函数,则调用默认构造函数)。
  3. 最后执行派生类构造函数的函数体。

析构函数的调用顺序则完全相反:

  1. 执行派生类析构函数的函数体。
  2. 按声明顺序的逆序调用成员对象的析构函数。
  3. 按继承顺序的逆序调用基类的析构函数。

如果基类构造函数需要参数,则必须在派生类构造函数的初始化列表中显式调用基类构造函数并传递参数。

如果基类有默认构造函数(无参构造函数或者所有参数都有默认值),则派生类构造函数可以不显式调用基类构造函数,编译器会自动调用基类的默认构造函数。

class Base {
public:
    Base(int x) : a(x) { cout << "Base constructor" << endl; }
    int a;
};

class Derived : public Base {
public:
    Derived(int x, int y) : Base(x), b(y) {   // 必须显式调用Base构造函数,因为Base没有默认构造函数
        cout << "Derived constructor" << endl;
    }
    int b;
};

int main() {
    Derived* obj = new Derived(10, 20);
    // 输出:
    // Base constructor
    // Derived constructor
    delete obj;
    return 0;
}

注意:在构造派生类对象时,基类部分是在派生类部分之前构造的。因此,在派生类构造函数中,可以安全地使用基类的成员(因为基类已经构造完成)。但是,在基类构造函数中,不应该调用派生类的虚函数(因为派生类部分还没有构造)。另外,如果派生类有多个基类(多重继承),则基类构造函数的调用顺序是按照派生类定义中基类出现的顺序,而不是初始化列表中的顺序。

class Base1 {
public:
    Base1() { cout << "Base1 constructor" << endl; }
};

class Base2 {
public:
    Base2() { cout << "Base2 constructor" << endl; }
};

class Derived : public Base2, public Base1 {   // 先继承Base2,再继承Base1
public:
    Derived() : Base1(), Base2() {   // 这里试图改变顺序,但实际上是无效的
        cout << "Derived constructor" << endl;
    }
};

int main() {
    Derived obj;
    // 输出:
    // Base2 constructor  (因为继承列表中Base2在前)
    // Base1 constructor
    // Derived constructor
    return 0;
}

在多重继承中,基类构造函数的调用顺序只与继承列表中的顺序有关,与派生类构造函数初始化列表中的顺序无关。

同样,在构造派生类对象时,还会构造派生类中的成员对象(即组合关系)。成员对象的构造顺序是按照它们在类中声明的顺序,而不是初始化列表中的顺序。


评论