我对大部分的对象导向编程(OOP
)理论有扎实的了解,但有一件事情经常让我困惑,那就是虚析构函数。
我曾以为无论如何,析构函数都会被调用,并且每个对象都要调用一次。
什么时候需要将析构函数声明为虚函数?为什么需要这样做呢?
我对大部分的对象导向编程(OOP
)理论有扎实的了解,但有一件事情经常让我困惑,那就是虚析构函数。
我曾以为无论如何,析构函数都会被调用,并且每个对象都要调用一次。
什么时候需要将析构函数声明为虚函数?为什么需要这样做呢?
虚基类析构函数是"最佳实践" - 你应该始终使用它们以避免(难以检测的)内存泄漏。使用虚基类析构函数,您可以确保调用了类继承链中所有析构函数(按正确顺序)。继承一个使用虚析构函数的基类会使派生类的析构函数自动成为虚函数,因此您不必在派生类析构函数声明中重新输入'virtual'。
const
全局变量和非const
全局变量的默认链接行为吗?即使你知道,我保证大多数人都不知道这两种链接类型的存在。 - user904963class A
{
public:
A() {}
virtual void foo()
{
cout << "This is A." << endl;
}
};
class B : public A
{
public:
B() {}
void foo()
{
cout << "This is B." << endl;
}
};
int main(int argc, char* argv[])
{
A *a = new B();
a->foo();
if(a != NULL)
delete a;
return 0;
}
将会打印出:
This is B.
virtual
,它会输出:This is A.
现在您应该明白何时使用虚析构函数。
B b{}; A& a{b}; a.foo();
。在 delete
之前检查 NULL
(应该是 nullptr
)- 带有不正确的缩进 - 是不必要的:delete nullptr;
被定义为无操作。如果有什么问题,你应该在调用 ->foo()
之前检查它,否则如果 new
失败,则可能会发生未定义行为。) - underscore_ddelete
是安全的(即,您不需要使用 if (a != NULL)
守卫)。 - James Adkisonstd::vector<Base*>
。当然,std::vector<Base&>
并不存在。 - user904963#include <iostream>
#include <memory>
using namespace std;
class Base
{
public:
Base(){
cout << "Base Constructor Called\n";
}
~Base(){ // not virtual
cout << "Base Destructor called\n";
}
};
class Derived: public Base
{
public:
Derived(){
cout << "Derived constructor called\n";
}
~Derived(){
cout << "Derived destructor called\n";
}
};
int main()
{
shared_ptr<Base> b(new Derived());
}
输出:
Base Constructor Called
Derived constructor called
Derived destructor called
Base Destructor called
unique_ptr
在C++14之后发生了变化,例如添加了make_unique
。也许委员会改进了unique_ptr
吗? - r0n9什么是虚析构函数或如何使用虚析构函数
类析构函数是一个与类同名的函数,前面加上 ~ ,它将重新分配由类分配的内存。为什么我们需要虚析构函数
看下面的示例,其中包含一些虚函数
该示例还说明了如何将字母转换为大写或小写
#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
~convertch(){};
};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
Letter[i] = Letter[i] + 32;
return Letter;
}
private:
char *Letter;
bool tolower;
};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
Letter[i] = Letter[i] - 32;
return Letter;
}
virtual ~MakeUpper()
{
cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};
int _tmain(int argc, _TCHAR* argv[])
{
convertch *makeupper = new MakeUpper("hai");
cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" ";
delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<" ";
delete makelower;
return 0;
}
#include "stdafx.h"
#include<iostream>
using namespace std;
// program to convert the lower to upper orlower
class convertch
{
public:
//void convertch(){};
virtual char* convertChar() = 0;
virtual ~convertch(){}; // defined the virtual destructor
};
class MakeLower :public convertch
{
public:
MakeLower(char *passLetter)
{
tolower = true;
Letter = new char[30];
strcpy(Letter, passLetter);
}
virtual ~MakeLower()
{
cout<< "called ~MakeLower()"<<"\n";
delete[] Letter;
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] + 32;
}
return Letter;
}
private:
char *Letter;
bool tolower;
};
class MakeUpper : public convertch
{
public:
MakeUpper(char *passLetter)
{
Letter = new char[30];
toupper = true;
strcpy(Letter, passLetter);
}
char* convertChar()
{
size_t len = strlen(Letter);
for(int i= 0;i<len;i++)
{
Letter[i] = Letter[i] - 32;
}
return Letter;
}
virtual ~MakeUpper()
{
cout<< "called ~MakeUpper()"<<"\n";
delete Letter;
}
private:
char *Letter;
bool toupper;
};
int _tmain(int argc, _TCHAR* argv[])
{
convertch *makeupper = new MakeUpper("hai");
cout<< "Eneterd : hai = " <<makeupper->convertChar()<<" \n";
delete makeupper;
convertch *makelower = new MakeLower("HAI");;
cout<<"Eneterd : HAI = " <<makelower->convertChar()<<"\n ";
delete makelower;
return 0;
}
#include <iostream>
using namespace std;
struct a
{
~a() {}
unsigned long long i;
};
struct b : a
{
~b() {}
unsigned long long j;
};
struct c : b
{
~c() {}
virtual void m3() {}
unsigned long long k;
};
struct d : c
{
~d() {}
virtual void m4() {}
unsigned long long l;
};
int main()
{
cout << "sizeof(a): " << sizeof(a) << endl;
cout << "sizeof(b): " << sizeof(b) << endl;
cout << "sizeof(c): " << sizeof(c) << endl;
cout << "sizeof(d): " << sizeof(d) << endl;
// No issue.
a* a1 = new a();
cout << "a1: " << a1 << endl;
delete a1;
// No issue.
b* b1 = new b();
cout << "b1: " << b1 << endl;
cout << "(a*) b1: " << (a*) b1 << endl;
delete b1;
// No issue.
c* c1 = new c();
cout << "c1: " << c1 << endl;
cout << "(b*) c1: " << (b*) c1 << endl;
cout << "(a*) c1: " << (a*) c1 << endl;
delete c1;
// No issue.
d* d1 = new d();
cout << "d1: " << d1 << endl;
cout << "(c*) d1: " << (c*) d1 << endl;
cout << "(b*) d1: " << (b*) d1 << endl;
cout << "(a*) d1: " << (a*) d1 << endl;
delete d1;
// Doesn't crash, but may not produce the results you want.
c1 = (c*) new d();
delete c1;
// Crashes due to passing an invalid address to the method which
// frees the memory.
d1 = new d();
b1 = (b*) d1;
cout << "d1: " << d1 << endl;
cout << "b1: " << b1 << endl;
delete b1;
/*
// This is similar to what's happening above in the "crash" case.
char* buf = new char[32];
cout << "buf: " << (void*) buf << endl;
buf += 8;
cout << "buf after adding 8: " << (void*) buf << endl;
delete buf;
*/
}
delete base_ptr
并且析构函数不是虚拟的,则会产生不希望的行为。然而,有几个假设需要明确说明:
std::string
。C++允许您自食其果。然而,这是您的责任,而不是基类没有虚析构函数的责任。如果您想了解该主题的不同观点,请阅读何时不应使用虚析构函数?
除非你有充分的理由不这样做,否则请将所有析构函数设置为虚函数。
否则会发生像这样的问题:
假设你有一个包含Apple和Orange对象的Fruit指针数组。
当你从Fruit对象集合中删除时, 除非~Fruit()是虚函数,否则~Apple()和~Orange()将无法被调用。
正确的示例:
#include <iostream>
using namespace std;
struct Fruit { // good
virtual ~Fruit() { cout << "peel or core should have been tossed" << endl; }
};
struct Apple: Fruit { virtual ~Apple() {cout << "toss core" << endl; } };
struct Orange: Fruit { virtual ~Orange() {cout << "toss peel" << endl; } };
int main() {
Fruit *basket[]={ new Apple(), new Orange() };
for (auto fruit: basket) delete fruit;
};
良好的输出
toss core
peel or core should have been tossed
toss peel
peel or core should have been tossed
错误的示例:
#include <iostream>
using namespace std;
struct Fruit { // bad
~Fruit() { cout << "peel or core should have been tossed" << endl; }
};
struct Apple: Fruit { virtual ~Apple() {cout << "toss core" << endl; } };
struct Orange: Fruit { virtual ~Orange() {cout << "toss peel" << endl; } };
int main() {
Fruit *basket[]={ new Apple(), new Orange() };
for (auto fruit: basket) delete fruit;
};
输出错误
peel or core should have been tossed
peel or core should have been tossed
virtual
的基本定义是它确定了一个类的成员函数是否可以在其派生类中被覆盖。
一个类的析构函数基本上在作用域的末尾被调用,但有一个问题,例如当我们在堆上(动态分配)定义一个实例时,我们应该手动删除它。
一旦指令被执行,基类析构函数被调用,但不是派生类的析构函数。
一个实际的例子是,在控制领域中,您必须操作效应器、执行器等。
在作用域的末尾,如果一个电源元素(执行器)的析构函数没有被调用,将会有致命后果。
#include <iostream>
class Mother{
public:
Mother(){
std::cout<<"Mother Ctor"<<std::endl;
}
virtual~Mother(){
std::cout<<"Mother D-tor"<<std::endl;
}
};
class Child: public Mother{
public:
Child(){
std::cout<<"Child C-tor"<<std::endl;
}
~Child(){
std::cout<<"Child D-tor"<<std::endl;
}
};
int main()
{
Mother *c = new Child();
delete c;
return 0;
}
_down_
都会被调用。virtual
确保它从顶部开始而不是中间开始。 - Mooing Duck