精华内容
下载资源
问答
  • 析构

    2020-06-18 19:19:26
    析构操作是一个对象的成员函数,在该对象离开作用域或通过delete操作明确的销毁时,被自动调用。析构操作与类同名,前面加个波浪符 (~)。例如,String类的析构操作的声明是:~String()。 当没有定义析构操作时,...

    https://docs.microsoft.com/en-us/cpp/cpp/destructors-cpp?view=vs-2019

    • 概述

    析构操作是一个对象的成员函数,在该对象离开作用域或通过delete操作明确的销毁时,被自动调用。析构操作与类同名,前面加个波浪符 (~)。例如,String类的析构操作的声明是:~String()。

    当没有定义析构操作时,编译器会自动生成一个默认的;很多情况下,它是足够的。当类中占用了需要释放的系统资源或者它们拥有的指针占用着内存。

    看看如下的一个String类:

    // spec1_destructors.cpp
    #include <string>

    class String {
    public:
       String( char *ch );  // Declare constructor
       ~String();           //  and destructor.
    private:
       char    *_text;
       size_t  sizeOfText;
    };

    // Define the constructor.
    String::String( char *ch ) {
       sizeOfText = strlen( ch ) + 1;

       // Dynamically allocate the correct amount of memory.
       _text = new char[ sizeOfText ];

       // If the allocation succeeds, copy the initialization string.
       if( _text )
          strcpy_s( _text, sizeOfText, ch );
    }

    // Define the destructor.
    String::~String() {
       // Deallocate the memory that was previously reserved
       //  for this string.
       delete[] _text;
    }

    int main() {
       String str("The piper in the glen...");
    }

    在上面的类String中的析构操作 String::~String使用了delete操作符来释放动态分配的内存。

    • 析构操作的声明

    析构操作与类同名,前面加个波浪符 (~)。

    析构操作的声明有几条基本规则:

    1. 没有参数
    2. 没有返回值
    3. 不能被声明带有限定符 const, volatile, or static。然而,它们可以被带有限定符 const, volatile, or static的对象在析构时调用。
    4. 可以带有virtual。当使用virtual 析构操作时,不需要知道对象的具体类型---该对象对应的正确的析构操作会被自动调用,这是通过virtual 函数机制实现的。说明,在一个抽象类中,析构操作可以被声明成纯虚函数。
    • 使用析构

    如下几个事件之一发生时就会调用析构操作:

    1. 局部对象离开作用域
    2. 使用new操作符生成的对象,通过明确使用delete来释放。
    3. 一个临时变量的生命期结束了。
    4. 程序运行完了,全局或静态对象存在。
    5. 使用析构函数的全称来明确调用。

    析构操作有两个限制:

    1. 不能获取它的地址
    2. 子类不能继承父类的析构操作
    • 析构操作的调用顺序

    当一个对象要被释放时,它的完整的析构操作的事件序列如下:

    1. 类的析构操作被调用,析构函数被执行。
    2. 非静态的成员对象的析构操作被按出现在类声明顺序的相反顺序执行。
    3. 对于“ non-virtual base classes”的析构操作按照声明的反方向顺序执行。
    4. 对于“virtual base classes ”的析构操作按照声明的反方向顺序执行。

    举例如下:
    // order_of_destruction.cpp
    #include <cstdio>

    struct A1      { virtual ~A1() { printf("A1 dtor\n"); } };
    struct A2 : A1 { virtual ~A2() { printf("A2 dtor\n"); } };
    struct A3 : A2 { virtual ~A3() { printf("A3 dtor\n"); } };

    struct B1      { ~B1() { printf("B1 dtor\n"); } };
    struct B2 : B1 { ~B2() { printf("B2 dtor\n"); } };
    struct B3 : B2 { ~B3() { printf("B3 dtor\n"); } };

    int main() {
       A1 * a = new A3;
       delete a;
       printf("\n");

       B1 * b = new B3;
       delete b;
       printf("\n");

       B3 * b2 = new B3;
       delete b2;
    }

    Output: A3 dtor
    A2 dtor
    A1 dtor

    B1 dtor

    B3 dtor
    B2 dtor
    B1 dtor

    • 虚拟基类

    对于“virtual base classes ”的析构操作按照声明(有向无环图)的反方向顺序执行,规则是“深度优先,从左到右,后序遍历”。下面的图描述了继承关系。

    下面显示了声明关系:
    class A
    class B
    class C : virtual public A, virtual public B
    class D : virtual public A, virtual public B
    class E : public C, public D, virtual public B

    为了决定一个E类型的对象的析构操作顺序,编译器通过如下算法建立一个列表:

    1. 遍历图的左侧,从图中最深的点开始,在这个实例中是从E开始.
    2. 执行左侧遍历,直到左侧路径所有节点被访问过。记录当前节点的名称。
    3. 再次访问它的上一个节点(向下或者向右)来确认一下已经被记下的节点是否是一个虚拟基类。
    4. 如果已经被记下的节点是一个虚拟基类,看看list中是否已经存在该节点。如果不是虚拟基类,则忽略。
    5. 如果已经被记下的节点没有在list中,则把它添加到list的底部。
    6. 向图的向上的路径和右侧的路径遍历。
    7. 如果图的向上的路径没有穷尽,则跳转到第2步。
    8. 当最后一条向上的路径被穷尽,记录当前节点名称。
    9. 如果底部节点没有再次出现在当前节点,则跳转到第3步。
    10. 如果底部节点 再次出现在当前节点,则遍历结束。

    因此,对于类E,list的排序是:

    The virtual base class A.

    The virtual base class B.

    The non-virtual base class C.

    The non-virtual base class D.

    The non-virtual base class E.

    析构操作顺序为:

    The non-virtual base class E.

    The non-virtual base class D.

    The non-virtual base class C.

    The virtual base class B.

    The virtual base class A

    简单描述一下该list的构造过程。

    1)首先定位到点E。

    2)根据最左边优先遍历,定位到点A。记录下A节点。

    3)A的上一个节点是C不是虚拟基类,忽略。

    4)把A记录到list中

    5)向C的另一个上侧路径遍历,定位到B。。记录下B节点。

    6)B的上一个节点是C不是虚拟基类,忽略。

    7)把B记录到list中
    8)节点C向上的路径都穷尽了,记录C节点。

    9)访问D节点,D不是虚拟基类,忽略。

    10)把C记录到list中

    10)把D记录到list中

    这种在一个继承关系图中存在类之间有相互依赖关系是很危险的。因为后面的子类会改变哪个是最左边路径,因此,它们也会改变构造和析构的顺序。

    • 非virtual基类

    The destructors for non-virtual base classes are called in the reverse order in which the base class names are declared. Consider the following class declaration:
    class MultInherit : public Base1, public Base2
    ...
    In the preceding example, the destructor for Base2 is called before the destructor for Base1.

    • Explicit destructor calls

    Calling a destructor explicitly is seldom necessary. However, it can be useful to perform cleanup of objects placed at absolute addresses. These objects are commonly allocated using a user-defined new operator that takes a placement argument. The delete operator cannot deallocate this memory because it is not allocated from the free store (for more information, see The new and delete Operators). A call to the destructor, however, can perform appropriate cleanup. To explicitly call the destructor for an object, s, of class String, use one of the following statements:
    s.String::~String();     // non-virtual call
    ps->String::~String();   // non-virtual call

    s.~String();       // Virtual call
    ps->~String();     // Virtual call
    The notation for explicit calls to destructors, shown in the preceding, can be used regardless of whether the type defines a destructor. This allows you to make such explicit calls without knowing if a destructor is defined for the type. An explicit call to a destructor where none is defined has no effect.

    • Robust programming

    A class needs a destructor if it acquires a resource, and to manage the resource safely it probably has to implement a copy constructor and a copy assignment.

    If these special functions are not defined by the user, they are implicitly defined by the compiler. The implicitly generated constructors and assignment operators perform shallow, memberwise copy, which is almost certainly wrong if an object is managing a resource.

    In the next example, the implicitly generated copy constructor will make the pointers str1.text and str2.text refer to the same memory, and when we return from copy_strings(), that memory will be deleted twice, which is undefined behavior:
    void copy_strings()
    {
       String str1("I have a sense of impending disaster...");
       String str2 = str1; // str1.text and str2.text now refer to the same object
    } // delete[] _text; deallocates the same memory twice
      // undefined behavior
    Explicitly defining a destructor, copy constructor, or copy assignment operator prevents implicit definition of the move constructor and the move assignment operator. In this case, failing to provide move operations is usually, if copying is expensive, a missed optimization opportunity.

    展开全文
  • Thread::~Thread() { Stop(); } void Thread::Stop() { if (!m_pkHandle) { return; } WaitForSingleObject((HANDLE)m_pkHandle, INFINITE); CloseHandle((HANDLE)m_pkHandle); m_pkHandle =...}
  • 析构和纯虚析构

    2021-02-02 20:02:03
    析构和纯虚析构 多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法带调用到子类的析构代码 解决方式:将父类的析构函数改为纯虚析构或者虚析构析构和纯虚析构的共性: 1.可以解决父类指针释放...

    虚析构和纯虚析构

    多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法带调用到子类的析构代码
    解决方式:将父类的析构函数改为纯虚析构或者虚析构

    虚析构和纯虚析构的共性:

    1.可以解决父类指针释放子类对象
    2.都必须要有具体的函数实现

    虚析构和纯虚析构的区别:
    如果是纯虚析构,该类属于抽象类,无法实例化对象

    #include<iostream>
    #include<string>
    using namespace std;
    class animal {
    public:
    	//构造函数
    	animal()
    	{
    		cout << "animal的构造函数调用" << endl;
    	}
        //纯虚函数
    	virtual void speak()
    	{
    		cout << "动物在说话" << endl;
    	}
    	//虚析构
    	virtual ~animal()
    	{
    		cout << "animal的析构函数调用" << endl;
    	}
    };
    class cat:public animal {
    public:
    	//构造函数
    	cat(string name) {
    		this->name = new string(name); //在堆区创建内存
    		cout << "cat的构造函数调用" << endl;
    	}
    	void speak()
    	{
    		cout <<*name<< "小猫在说话" << endl;
    	}
    	//写一个析构函数释放堆区内存
    	virtual ~cat() {
    		if (name != NULL)
    		{
    			cout << "cat的析构函数调用" << endl;
    			delete name; //释放堆区内存
    			name = NULL;
    		}
    	}
    	string* name;
    };
    void test()
    {
    	animal* a =new cat("tom");
    	a->speak();
    	delete a; //如果不在析构函数前加virtual,就只会调用父类析构函数
    }
    int main()
    {
    	test();
    	system("pause");
    	return 0;
    }
    

    纯虚析构写法:virtual ~animal()=0
    纯虚析构定义实现和虚析构一样:
    animal::~animal(){}

    展开全文
  • C++学习笔记之虚析构和纯虚析构/虚析构和纯虚析构的学习/虚析构和纯虚析构学习 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。 解决办法: 将父类中的析构函数改为虚析构...

    C++学习笔记之虚析构和纯虚析构/虚析构和纯虚析构的学习/虚析构和纯虚析构学习
    多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。
    解决办法: 将父类中的析构函数改为虚析构或者纯虚析构
    虚析构和纯虚析构共性:
    可以解决父类指针释放子类对象
    都需要有具体的函数实现
    虚析构和纯虚析构区别:
    如果是纯虚析构,该类是抽象类,无法实例化对象。
    虚析构语法

    virtual ~类名(){}
    

    纯虚析构语法

    virtual ~类名() = 0;
    类名::~类名(){}
    

    虚析构或纯虚析构就是用来解决父类指针释放子类对象
    如果子类中没有堆区数据,可以不写虚析构函数或纯虚析构
    拥有纯虚析构函数的类也属于抽象类

    #include <iostream>
    #include <string>
    using namespace std;
    
    //多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码
    //解决方式:将父类中的析构函数改为 虚析构 或者 纯虚析构
    //虚析构 和 纯虚析构共性
    //可以解决父类指针释放子类对象
    //都需要有具体的函数实现
    
    //虚析构和纯虚析构区别:
    //如果是纯虚析构,该类属于抽象类,无法实例化对象
    
    class Animal   
    {
    public:
    	Animal()
    	{
    		cout << "Animal 构造函数调用 !" << endl;
    	}
    	virtual void speak() = 0;
    	//析构函数加上virtual关键字,变成虚析构函数
    	//virtual ~Animal()
    	//{
    	//	cout << "Animal虚析构函数调用!" << endl;
    	//}
    
    	/*~Animal()
    	{
    		cout << "Animal析构函数调用!" << endl;
    	}*/
    
    	virtual ~Animal() = 0;
    };
    Animal::~Animal()
    {
    	cout << "Animal 纯虚析构函数调用!" << endl;
    }
    //和包含普通纯虚函数的类一样,包含了纯虚析构函数的类也是一个抽象类。不能被实例化
    
    class Cat : public Animal
    {
    public:
    	Cat(string name)
    	{
    		cout << "Cat构造函数调用!" << endl;
    		m_Name = new string(name);
    	}
    	virtual void speak()
    	{
    		cout << *m_Name << "在说话" << endl;
    	}
    	~Cat()
    	{
    		cout << "Cat的析构函数!" << endl;
    		if (this->m_Name != NULL)
    		{
    			delete m_Name;
    			m_Name = NULL;
    		}
    	}
    public:
    	string* m_Name;
    };
    
    void test()
    {
    	Animal* animal = new Cat("小花");
    	animal->speak();
    	//通过父类指针去释放,会导致子类对象可能清理不干净,造成内存泄漏
    	//怎么解决? 给基类添加一个虚析构函数
    	//虚析构函数就额是用来解决通过父类指针是释放子类对象
    	delete animal;
    }
    int main()
    {
    	test();
    	return 0;
    }
    
    //总结:虚析构或纯虚析构就是用来解决通过父类指针释放子类对象
    //如果子类中没有堆区数据,可以不写虚析构或纯虚虚构
    //拥有纯虚析构的类也属于抽象类
    

    C++学习笔记之虚析构和纯虚析构/虚析构和纯虚析构的学习/虚析构和纯虚析构学习

    展开全文
  • C++虚析构与纯虚析构

    2021-03-15 22:10:14
    C++虚析构与纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码 解决方法: 将父类中的析构函数改为虚析构或纯虚析构 纯虚析构和虚析构共性: 1、可以解决父类指针释放...

    C++虚析构与纯虚析构

    多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用子类的析构代码

    解决方法:
    将父类中的析构函数改为虚析构或纯虚析构

    纯虚析构和虚析构共性


    1、可以解决父类指针释放子类对象的问题;
    2、都需要具体的函数实现;
    纯虚析构是类内声明,类外实现。

    纯虚析构和虚析构区别:

    如果是纯虚析构,该类属于抽象类,无法实例化对象

    虚析构语法:

    virtual ~类名()
    {
    	//code
    }
    

    纯虚析构语法

    virtual ~类名()= 0;//类内声明
    
    
    //类外实现
    类名:~类名()
    {
    /code
    }
    
    #include <iostream>
    #include <string>
    using namespace std;
    
    //动物类
    class Animal
    {
    public:
    	Animal()
    	{
    		cout << "Animal 的构造函数在调用" << endl;
    	}
    	//纯虚函数
    	virtual void Speak() = 0;
    
    	//如果不设为virtual,则不会调用子类的析构函数
    	/*virtual ~Animal()
    	{
    		cout << "Animal 的虚析构函数在调用" << endl;
    	}*/
    	//利用虚析构可以解决,父类指针释放子类对象时不干净的问题
    	//纯虚析构
    	//纯虚析构需要声明,也需要实现
    	//有了纯虚析构后,这个类也属于抽象类,需要实例化
    	virtual ~Animal() = 0;
    };
    
    //纯虚析构在类内声明,类外实现
    Animal:: ~Animal()
    {
    	cout << "Animal 的纯虚析构函数在调用" << endl;
    }
    //猫类
    class Cat :public Animal
    {
    public:
    	Cat(string name)
    	{
    		cout << "Cat 的构造函数在调用" << endl;
    		m_Name = new string(name);
    	}
    	//重写:函数返回类型 函数名 参数列表完全相同
    	void Speak()
    	{
    		cout << *m_Name << "小猫在说话" << endl;
    	}
    
    	~Cat()
    	{
    		if (m_Name != NULL)
    		{
    			cout << "Cat 的析构函数在调用" << endl;
    			//如果父类析构不为虚析构,父类指针在析构时候,不会调用子类析构函数,导致子类如果有堆区属性,导致内存泄漏
    			delete m_Name;
    			m_Name = NULL;
    		}
    	}
    	string *m_Name;
    };
    
    
    //测试代码
    void test01()
    {
     
    	Animal *animal = new Cat("Tom");
    	animal->Speak();
    	delete animal;
    }
    
    int main()
    {
    	test01();
    	system("pause");
    	return 0;
    }
    

    总结:

    1、虚析构和纯虚析构就是用来解决通过父类对象指针释放子类对象;
    2、如果子类中每有堆区数据,可以不写为虚析构或纯虚析构;
    3、拥有纯虚析构的类也属于抽象类。

    展开全文
  • c#析构构造函数c#析构构造函数c#析构构造函数c#析构构造函数
  • 析构和纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 解决方法:将父类中的析构函数改为虚析构或者纯虚析构析构和纯虚析构共性: 1.可以解决父类指针释放...
  • c++虚析构和纯虚析构

    2020-12-26 10:51:26
    4.7.5 虚析构和纯虚析构 纯虚析构的使用场景:多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。(1.父类指针指向子类对象,2.子类在堆区有数据) 解决方式:将父类中的析...
  • 析构与纯虚析构

    2019-10-08 18:35:20
    在实现多态时, 如果子类中有属性开辟到堆区, 那么父类指针释放子类对象时无法释放干净(即无法调用到子类的析构代码) 解决方式: 将父类中的析构函数改为虚析构或纯虚析构 实现语法:(二者只能有一个) 虚析构: (即析构...
  • C++虚析构和纯虚析构

    2020-11-08 12:34:56
    析构和纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 解决方式:将父类中的析构函数改为虚析构或者纯虚析构析构和纯虚析构共性: 可以解决父类指针释放...
  • 文章目录虚析构和纯虚析构的共性:虚析构和纯虚析构的区别语法示例虚析构纯虚析构 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 解决方式:将父类中的析构函数改为虚析构...
  • 析构和纯虚析构原理 直接上代码:请根据编号查看代码说明。 先总结: 虚析构或纯虚析构就是用来解决通过父类指针释放子类对象 如果子类中没有堆区数据,可以不写为虚析构或纯虚析构 拥有纯虚析构函数的类也...
  • 析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。 析构器用关键字 deinit 来标示,类似于构造器要用 init 来标示。 1. 析构过程原理 Swift 会自动释放不再需要的实例以释放资源。 Swift ...
  • 析构

    2019-07-08 17:33:17
    析构器写法是deinit{} 析构器是在类被销毁之前自动调用的一个方法。 析构器不允许我们手动调动。 析构器不允许显示调用父类的析构器。 析构器不存在重写,也不用加override关键字。如果子类和父类同时都写了析构器,...
  • C++:虚析构和纯虚析构

    2021-04-03 16:26:15
    多态使用时,如果子类有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码。 解决方式:将父类中的析构函数改为虚析构或纯虚析构加粗样式 虚析构和纯虚析构共性: 可以解决父类指针释放子类对象 都...
  • 析构

    2021-02-28 23:35:47
    1.要在基类中用虚析构 Q:为什么呢? A:因为当声明基类类型指向子类的对象时,如果父类声明普通析构,那么子类析构函数就不会被调用;只有父类声明虚析构的时候,通过虚函数表会调用到子类析构函数,这样才不会...
  • 析构和纯虚析构是为了解决以下问题: 多态使用时,如果子类中有属性开辟到堆区,那么父类指针在释放时无法调用到子类的析构代码 虚析构和纯虚析构共性: 1、可以解决父类指针释放子类对象 2、都需要有具体的函数...
  • Swift 析构过程

    2021-01-06 02:48:58
    Swift 析构过程 在一个类的实例被释放之前,析构函数被立即调用。用关键字deinit来标示析构函数,类似于初始化函数用init来标示。析构函数只适用于类类型。 析构过程原理 Swift 会自动释放不再需要的实例以释放资源...
  • C++虚析构和纯虚析构问题小记   在我们使用多态的时候,当我们不对父类析构函数做额外操作的话,它仅仅是析构父类自身,不会调用子类的析构,所以可能会导致释放不干净;但是对父类的析构函数加上virtual关键字,...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 129,640
精华内容 51,856
关键字:

析构