构造、析构函数可否为virtual

本文解析了构造函数为何不能声明为虚函数,而析构函数在何种情况下必须声明为虚函数。探讨了虚函数的实现机制及其对内存管理的影响,通过实例说明了虚析构函数的重要性。

构造函数不能声明为虚函数,析构函数可以声明为虚函数,而且有时是必须声明为虚函数。

不建议在构造函数和析构函数里面调用虚函数。

构造函数不能声明为虚函数的原因是:

解释一:所谓虚函数就是多态情况下只执行一个。而从继承的概念来讲,总是要先构造父类对象,然后才能是子类对象。如果构造函数设为虚函数,那么当你在构造父类的构造函数时就不得不显示的调用构造。还有一个原因就是为了防错,试想如果你在子类中一不小心重写了个跟父类构造函数一样的函数,那么你的父类的构造函数将被覆盖,也即不能完成父类的构造.就会出错。

解释二:虚函数的主要意义在于被派生类继承从而产生多态。派生类的构造函数中,编译器会加入构造基类的代码,如果基类的构造函数用到参数,则派生类在其构造函数的初始化列表中必须为基类给出参数,就是这个原因。

虚函数的意思就是开启动态绑定,程序会根据对象的动态类型来选择要调用的方法。然而在构造函数运行的时候,这个对象的动态类型还不完整,没有办法确定它到底是什么类型,故构造函数不能动态绑定。(动态绑定是根据对象的动态类型而不是函数名,在调用构造函数之前,这个对象根本就不存在,它怎么动态绑定?)

编译器在调用基类的构造函数的时候并不知道你要构造的是一个基类的对象还是一个派生类的对象。

析构函数设为虚函数的作用:

解释:在类的继承中,如果有基类指针指向派生类,那么用基类指针delete时,如果不定义成虚函数,派生类中派生的那部分无法析构。

例:

#include "stdafx.h"

#include "stdio.h"

class A

{

public:

A();

virtual ~A();

};

A::A()

{

}

A::~A()

{

printf("Delete class AP/n");

}

class B : public A

{

public:

B();

~B();

};

B::B()

{ }

B::~B()

{

printf("Delete class BP/n");

}

int main(int argc, char* argv[])

{

A *b=new B;

delete b;

return 0;

}

输出结果为:Delete class B

Delete class A

如果把A的virtual去掉:那就变成了Delete class A

因此在类的继承体系中,基类的析构函数不声明为虚函数容易造成内存泄漏。所以如果你设计一定类可能是基类的话,必须要声明其为虚函数。正如Symbian中的CBase一样。Note:

1. 如果我们定义了一个构造函数,编译器就不会再为我们生成默认构造函数了。

2. 编译器生成的析构函数是非虚的,除非是一个子类,其父类有个虚析构,此时的函数虚特性来自父类。

3. 有虚函数的类,几乎可以确定要有个虚析构函数。

4. 如果一个类不可能是基类就不要申明析构函数为虚函数,虚函数是要耗费空间的。

5. 析构函数的异常退出会导致析构不完全,从而有内存泄露的问题。最好是提供一个管理类,在管理类中提供一个方法来析构,调用者再根据这个方法的结果决定下一步的操作。

6. 在构造函数不要调用虚函数。在基类构造的时候,虚函数是非虚,不会走到派生类中,既是采用的静态绑定。显然的是:当我们构造一个子类的对象时,先调用基类的构造函数,构造子类中基类部分,子类还没有构造,还没有初始化,如果在基类的构造中调用虚函数,如果可以的话就是调用一个还没有被初始化的对象,那是很危险的,所以C++中是不可以在构造父类对象部分的时候调用子类的虚函数实现。但是不是说你不可以那么写程序,你这么写,编译器也不会报错。只是你如果这么写的话编译器不会给你调用子类的实现,而是还是调用基类的实现。

7. 在析构函数中也不要调用虚函数。在析构的时候会首先调用子类的析构函数,析构掉对象中的子类部分,然后在调用基类的析构函数析构基类部分,如果在基类的析构函数里面调用虚函数,会导致其调用已经析构了的子类对象里面的函数,这是非常危险的。

8. 记得在写派生类的拷贝函数时,调用基类的拷贝函数拷贝基类的部分,不能忘记了。

如果一个类是作为基类使用,那么他的虚构函数一定要是虚的,即用virtual关键字(参数为零则为纯虚函数).
否则会有内存泄漏(很重要),因为当用基类的指针删除一个派生类的对象时,要调用派生类的析构函数.但是
其子类或者子子类可以的析构函数可以是虚函数,也可以不是虚函数.(不加virtual 关键字则不会调用派生
类的析构函数,而上面用了ClxBase *pTest = new ClxDerived;语句也就是new的ClxDerived对象没有 销毁,所以产生内存泄漏)
2.类中的虚函数,如果一个类中的函数被声明成为虚函数,那么其子类不用在声明为虚函数(当子类还有子类时),
也可以声明为虚函数.结果是一样的.同虚析构函数的道理是一样的.当然,并不是要把所有类的析构函数都写
成虚函数。因为当类里面有虚函数的时候,编译器会给类添加一个虚函数表,里面来存放虚函数指针,这样就
会增加类的存储空间。所以,只有当一个类被用来作为基类的时候,才把析构函数写成虚函数。

具体例子:

#include "iostream.h"

class ClxBase
{
public:
ClxBase() {};
virtual ~ClxBase() { cout << "Output from the destructor of class ClxBase!" << endl; };
virtual void DoSomething() { cout << "Do something in class ClxBase!" << endl; };
};


class ClxDerived : public ClxBase
{
public:
ClxDerived() {};
virtual~ClxDerived() { cout << "Output from the destructor of class ClxDerived!" << endl; };

//此处的virtual可以去掉
virtual void DoSomething() { cout << "Do something in class ClxDerived!" << endl; };

//此处的virtual可以去掉
};


class ClxThrived : public ClxDerived
{
public:
ClxThrived(){};
virtual ~ClxThrived(){cout << "Output from the destructor of class ClxThrived!" << endl;};

//此处的virtual可以去掉
virtual void DoSomething(){cout << "Do something in class ClxThrived!" << endl;}

//此处的virtual可以去掉
};
void main()
{
ClxBase *pTest1 = new ClxBase;
pTest1->DoSomething();
delete pTest1;//1

ClxBase *pTest2 = new ClxDerived;
pTest2->DoSomething();
delete pTest2;//2 用基类的指针删除一个派生类的对象时

ClxDerived *pTest3 = new ClxDerived;
pTest3->DoSomething();
delete pTest3;//3

ClxBase *pTest4 = new ClxThrived;
pTest4->DoSomething();
delete pTest4;//4 用基类的指针删除一个派生类的对象时

ClxDerived *pTest5 = new ClxThrived;
pTest5->DoSomething();
delete pTest5;//5 用基类的指针删除一个派生类的对象时

ClxThrived *pTest6 = new ClxThrived;
pTest6->DoSomething();
delete pTest6;//6
}

<think>好的,用户问的是构造函数析构函数能否传递。首先,我需要明确“传递”在这里的具体含义。可能是指参数传递、继承中的调用,或者像函数指针那样的传递。接下来,我得分别分析构造函数析构函数在这几种情况下的可能性。 构造函数方面,通常用于初始化对象,不能被显式调用或作为函数指针传递。参数传递的话,构造函数的参数是在创建对象时传入的,比如类A的构造有参数,实例化时需要提供。但构造函数本身不能像普通函数那样传递或赋值。至于继承,派生类的构造函数会调用基类的,但这属于自动调用,不是传递。 析构函数方面,主要用于清理资源,同样不能显式调用或作为函数指针。参数方面,析构函数没有参数,所以无法传递参数。继承中,析构函数会自动调用基类的,但同样不算传递。虚析构函数的情况,通过基类指针删除派生类实例时会调用正确的析构函数,但这属于多态机制,而非传递。 接着考虑用户可能的实际需求。他们可能想动态构造对象,这时可以使用工厂模式,通过函数返回对象,或者传递工厂函数。或者他们想实现回调机制,这种情况下可以用函数指针或std::function包装普通函数,而非构造函数。 需要提醒用户注意,直接传递构造/析构函数不符合C++语法,但可以通过设计模式间接实现类似功能。同时要强调资源管理的重要性,比如使用智能指针避免内存泄漏,尤其是在涉及多态时,确保析构函数为虚函数。 总结时,分点说明构造函数析构函数在参数传递、继承、函数指针等方面的情况,并给出替代方案,确保回答结构清晰,解决用户潜在的问题。</think>构造函数析构函数的"传递性"需要从以下几个层面理解: 1. **构造函数的特点** - ❌ **不能作为函数指针传递** 构造函数没有返回值类型,无法通过函数指针调用。尝试`&ClassName::ClassName`会编译报错 - ✅ **参数可以传递** 在创建对象时,可以通过构造函数参数初始化成员: ```cpp class MyClass { public: MyClass(int x) { /* 使用x初始化 */ } }; MyClass obj(10); // 参数传递到构造函数 ``` - ✅ **继承中的构造函数调用** 派生类构造函数会隐式调用基类构造函数: ```cpp class Base { public: Base(int) {} }; class Derived : public Base { public: Derived() : Base(5) {} // 显式调用基类构造函数 }; ``` 2. **析构函数的特点** - ❌ **不能主动调用或传递** 析构函数没有参数且不可重载,以下操作非法: ```cpp obj.~MyClass(); // 特殊情况:允许但危险(可能重复释放) void (MyClass::*dtor)() = &MyClass::~MyClass; // 编译错误 ``` - ✅ **自动调用链** 对象销毁时会自动触发析构函数调用链: ```cpp class Base { public: virtual ~Base() {} }; class Derived : public Base { public: ~Derived() override {} }; Base* p = new Derived(); delete p; // 正确调用Derived::~Derived() -> Base::~Base() ``` 3. **替代方案** - 需要动态构造时 → 使用工厂模式: ```cpp std::function<MyClass*()> factory = []{ return new MyClass(5); }; ``` - 需要类似析构的回调 → 使用RAII模式: ```cpp class Resource { public: ~Resource() { /* 自动释放资源 */ } }; ``` 4. **重要注意事项** - 多态基类必须声明虚析构函数 - 避免手动调用析构函数(除非使用placement new) - C++11后可用`= default`生成默认构造/析构函数 - 移动构造函数(C++11)可以实现资源转移的特殊"传递" 结论:构造函数析构函数本身不能像普通函数那样传递,但通过合理的类设计模式,可以实现类似的控制流效果。核心是要遵循C++的对象生命周期管理机制。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值