构造析构顺序
构造函数和析构函数的调用顺序
构造函数的调用顺序:
当建立一个对象时,首先调用基类的构造函数,然后调用下一个派生类的构造函数,依次类推,直至到达最底层的目标派生类的构造函数为止。
析构函数的调用书序:
当删除一个对象时,首先调用该派生类的析构函数,然后调用上一层基类的析构函数,依次类推,直到到达最顶层的基类的析构函数为止。
简单的说,构造函数是“自上向下”调用,析构函数是“自下而上”调用。
| 构造函数的调用顺序为:调用基类的构造函数->调用成员对象的构造函数->调用自身的构造函数。构造函数的调用次序完全不受构造函数初始化列表的表达式中的次序影响,与基类的声明次数和成员对象在函数中的声明次序有关。 |
如果基类里面有成员对象也会先调用基类里面的成员对象构造函数
#include <iostream>
class A {
public:
A() { std::cout << "构造 A" << std::endl; }
~A() { std::cout << "析构 A" << std::endl; }
};
class B {
public:
B() { std::cout << "构造 B" << std::endl; }
~B() { std::cout << "析构 B" << std::endl; }
private:
A a_;
};
class C : public B {
public:
C() { std::cout << "构造 C" << std::endl; }
~C() { std::cout << "析构 C" << std::endl; }
};
int main() {
B b;
C c;
}
| 构造 A 构造 B 构造 A 构造 B 构造 C 析构 C 析构 B 析构 A 析构 B 析构 A |
定义
C++类有继承时,析构函数必须为虚函数 如果不是虚函数,则使用时可能存在内在泄漏的问题。
原因
在公有继承中,基类对派生类及其对象的操作,只能影响到那些从基类继承下来的成员。如果想要用基类对非继承成员进行操作,则要把基类的这个操作(函数)定义为虚函数。析构函数同样需要如此。
如果用基类的指针来删除派生类的对象,而这个基类有一个非虚的析构函数,则结果是未定义的。后果是对象的派生部分不会被销毁,然而,基类部分很可能已被销毁,这就导致产生了“部分析构”的情况,这是一个内存泄露。
其实都是虚表的而作用
作用
这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用。
当然,并不是要把所有类的析构函数都写成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。
总结一下虚析构函数的作用:
(1)如果父类的析构函数不加virtual关键字
当父类的析构函数不声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,只调动父类的析构函数,而不调动子类的析构函数。
代码
#include <iostream>
class A {
public:
A() { std::cout << "构造 A" << std::endl; }
~A() { std::cout << "析构 A" << std::endl; }
};
class B {
public:
B() { std::cout << "构造 B" << std::endl; }
~B() { std::cout << "析构 B" << std::endl; }
private:
A a_;
};
class C : public B {
public:
C() { std::cout << "构造 C" << std::endl; }
~C() { std::cout << "析构 C" << std::endl; }
};
int main() {
B* c = new C();
delete c;
}
| 构造 A 构造 B 构造 C 析构 B 析构 A 可以看出来,没有调用虚表,直接会调用基类的析构函数,不会调用子类的析构函数> |
(2)如果父类的析构函数加virtual关键字
当父类的析构函数声明成虚析构函数的时候,当子类继承父类,父类的指针指向子类时,delete掉父类的指针,先调动子类的析构函数,再调动父类的析构函数。
代码
#include <iostream>
class A {
public:
A() { std::cout << "构造 A" << std::endl; }
~A() { std::cout << "析构 A" << std::endl; }
};
class B {
public:
B() { std::cout << "构造 B" << std::endl; }
virtual ~B() { std::cout << "析构 B" << std::endl; }
private:
A a_;
};
class C : public B {
public:
C() { std::cout << "构造 C" << std::endl; }
~C() { std::cout << "析构 C" << std::endl; }
};
int main() {
B* c = new C();
delete c;
}
| 构造 A 构造 B 构造 C 析构 C 析构 B 析构 A |
每一个类会有一个虚表,对象生成的时候会自动把对象的虚指针指向类的虚表,注意虚指针是属于基类的,所以基类是可以访问到子类的虚指针的,所以当delete 基类的指针的时候,通过虚表调用子类的析构函数,再析构父类的析构函数,这时候就不会造成内存泄漏
1069

被折叠的 条评论
为什么被折叠?



