C++析构机制实验
内存管理是c++不同于其它高级语言的一门很深的学问,要想管理好内存,首先要熟悉c++的特性,这样才能在写代码时做出合适的假设,避免内存泄露等难以发现的顽疾。
众所周知析构一般是用来释放堆栈空间的。对于程序员来说栈空间基本上都会由编译器帮我们处理,尤其需要我们来经营的是堆空间。如果一个结构体/类不存在对象指针,那么它基本可以不用写析构函数。
下文概念执行析构:执行析构指释放对象的资源,需要注意执行析构不等同于执行析构函数(这其中还包括一个隐式的递归资源释放,下文会体现)
下文每提到一个机制,都会给出一个相应的测试用例,请先思考输出结果后再进行执行比对。
析构的发生
当一个栈帧在释放时,其上的结构若再无法被其它代码直接访问则会被自动析构
- 如果一个栈帧结束,会执行栈上所有结构的析构
//测试用例
class Bird
{
public:
Bird(){}
~Bird()
{
cout<<"Bird was killed";
}
};
int main()
{
Bird bird;
}
- 如果一个栈帧结束,但某个结构作为返回值返回并被父栈帧接收,那么就不会在子栈帧被析构。反之,如果没返回,或者返回了但没有接收,都会被执行析构
struct T
{
char name;
T(char name):name(name){}
~T()
{
cout<<name<<": I'm deleted!"<<endl;
}
};
T getTs()
{
T n('N');//没有被返回
T r('R');
return r;
}
T getT()
{
T r('!');
return r;
}
int main()
{
T t=getTs();//返回被接收
getT();//返回未接收
cout<<"main over"<<endl;
}
析构的自动递归
执行析构不仅限于执行"析构函数",还包括隐式地析构成员对象
- 当一个对象被析构,它的成员对象会被递归析构。(但是他的成员指针不会,堆上的需要我们手动delete)
- 一个对象的成员指针需要在析构函数内手动检查并析构(delete)。
- 析构的顺序总是先调用析构函数,后析构成员对象
class Wing
{
public:
Wing(){}
~Wing()
{
cout<<"Wings were broken";
}
};
class Bird
{
Wing wing;
public:
Bird(){}
~Bird()
{
cout<<"Bird was killed\n";
}
};
int main()
{
Bird bird;
}
通过delete来手动执行堆上对象的析构
class Wing
{
public:
Wing(){}
~Wing()
{
cout<<"Wings were broke";
}
};
class Bird
{
Wing wing;
public:
Bird(){}
~Bird()
{
cout<<"Bird was killed\n";
}
};
int main()
{
Bird* bird=new Bird();
delete bird;
}
- 析构函数不止"析构函数",调用析构函数等同于调用析构
- c++不建议通过显式调用析构函数来析构对象
class Wing
{
public:
Wing(){}
~Wing()
{
cout<<"Wings were broke\n";
}
};
class Bird
{
Wing wing;
public:
Bird(){}
~Bird()
{
cout<<"Bird was killed\n";
}
};
int main()
{
Bird* bird=new Bird();
bird->~Bird();
cout<<"main over"<<endl;
}
继承中的析构
- 一个派生类在析构时会先析构自身,后析构基类
struct DNA
{
~DNA()
{
cout<<"DNA"<<": I'm deleted!"<<endl;
}
};
struct Animal
{
DNA dna;
Animal(){}
~Animal()
{
cout<<"Animal"<<": I'm deleted!"<<endl;
}
};
struct Paw
{
~Paw()
{
cout<<"Paws"<<": I'm deleted!"<<endl;
}
};
struct Cat : public Animal
{
Paw paw;
Cat(){}
~Cat()
{
cout<<"Cat"<<": I'm deleted!"<<endl;
}
};
int main()
{
Cat cat;
}
- 一个基指针指向派生对象,析构时只会析构基对象
struct Animal
{
Animal(){}
~Animal()
{
cout<<"Animal"<<": I'm deleted!"<<endl;
}
};
struct Cat : public Animal
{
Cat(){}
~Cat()
{
cout<<"Cat"<<": I'm deleted!"<<endl;
}
};
int main()
{
Animal *anim=new Cat;
delete anim;
}
- 若将基虚构函数虚化,则可正确析构派生对象
所以说基类的析构函数一定要加virtual!
struct Animal
{
Animal(){}
virtual ~Animal()
{
cout<<"Animal"<<": I'm deleted!"<<endl;
}
};
struct Cat : public Animal
{
Cat(){}
~Cat()
{
cout<<"Cat"<<": I'm deleted!"<<endl;
}
};
int main()
{
Animal *anim=new Cat;
delete anim;
}