为什么不完整类型的智能指针数据成员和原始指针数据成员在父父进行破坏时会有不同的行为?

时间:2015-03-15 15:53:32

标签: c++ c++11 smart-pointers

在以下代码中:

智能指针数据成员pImpl(类Impl)和原始指针pc(类CAT)都是不完整的数据类型,Widget.h中没有这两个类的定义

// widget.h

#ifndef W_H_
#define W_H_
#include <memory>

class Widget { 
    public:
        Widget();
        ~Widget() {    //
            delete pc; // I know I should put ~Widget to .cpp
                       // I just want to show the difference in behavior
                       // between raw pointer and smart pointer(both has incomplete type)
                       // when widget object destructs 
        }
    private:
        struct Impl; 
        std::shared_ptr<Impl> pImpl;  // use smart pointer
        struct CAT;
        CAT *pc;  //raw pointer
};
#endif

// widget.cpp

#include "widget.h"

#include <string>
#include <vector>
#include <iostream>
using namespace std;
struct Widget::CAT 
{
    std::string name;
    CAT(){cout<<"CAT"<<endl;}
    ~CAT(){cout<<"~CAT"<<endl;}
};      


struct Widget::Impl {
    std::string name;
    Impl(){cout<<"Impl"<<endl;}
    ~Impl(){cout<<"~Impl"<<endl;}
};


Widget::Widget()  
    : pImpl(std::make_shared<Impl>()),
      pc(new CAT)
{} 

// main.cpp中

#include "widget.h"
int main()
{
    Widget w;
}

//输出

  

Impl

     

CAT

     

〜默认地将Impl

对于原始指针数据成员,当widget对象被销毁时,其destuctor 不被称为

shared_ptr数据成员,其析构函数已被正确调用。

据我了解,在Widget :: ~Widget()中它应该生成一些 自动编码如下:

        ~Widget() {
            delete pc; // wrote by me

            // generated by compiler
            delete pImpl->get();
        }

为什么当窗口小部件被破坏时,shared_ptr数据成员和原始数据成员会有不同的行为?

我在Linux中使用g ++ 4.8.2测试代码

================================ EDIT ============== ================= 根据答案,原因是:

编译器生成的代码是 NOT

        ~Widget() {
            delete pc; // wrote by me

            // generated by compiler
            delete pImpl->get();
        }

它可能类似于:

        ~Widget() {
            delete pc; // wrote by me

            // generated by compiler
            pimpl.deleter(); //deleter will be initailized while pimpl object is initialized
        }

3 个答案:

答案 0 :(得分:13)

因为您在头文件中转发声明“CAT”,所以您的数据类型不完整。使用此信息,编译器会陷入未定义的行为,并且可能无法调用析构函数。

标准是什么

  

如果要删除的对象在该点处具有不完整的类类型   删除和完整的类有一个非平凡的析构函数或   解除分配函数,行为未定义。

您可以在此处找到详细说明:Why, really, deleting an incomplete type is undefined behaviour? 只需将struct声明移到类定义之前就可以解决问题:

struct CAT
{
    std::string name;
    CAT(){std::cout<<"CAT"<<std::endl;}
    ~CAT(){std::cout<<"~CAT"<<std::endl;}
};

class Widget {
    public:
        Widget();
        ~Widget() {
            delete pc; // I know we should put this code to cpp
                       // I am just want to show the difference behavior
                       // between raw pointer and smart pointer
                       // when widget object destruct
        }
    private:
        struct Impl;
        std::shared_ptr<Impl> pImpl;  // use smart pointer
        CAT *pc;  //raw pointer
};

输出

Impl
CAT
~CAT
~Impl

前向声明可以加快编译时间,但在需要有关数据类型的更多信息时可能会出现问题。

但为什么它适用于智能指针? 这是一个更好的解释:Deletion of pointer to incomplete type and smart pointers

基本上,shared_ptr在初始化或重置指针时只需要声明。这意味着它在声明的那一刻不需要完整的类型。

  

此功能不是免费的:shared_ptr必须创建和存储   指向删除函子的指针;通常这是通过存储   删除作为存储强弱引用的块的一部分   计数或通过指针作为指向该块的块的一部分   删除(因为你可以提供自己的删除)。

答案 1 :(得分:5)

解释

您正在尝试删除不完整类型的对象,如果要删除的对象类型具有非平凡的析构函数,则这是未定义的行为

有关此问题的更多信息,请参阅标准[expr.delete]以及以下链接:


注意Widget::Cat的析构函数是非平凡的,因为它是用户声明的;反过来,这意味着它没有被调用。



解决方案

要解决问题,只需提供Widget::Cat的定义,以便在执行delete pc



为什么它适用于 shared_ptr

使用shared_ptr时它的工作原理是“删除点”在您实际构建 shared_ptr 实例之前不会发生(通过{{1}实际上Deleter被实例化的时候。

答案 2 :(得分:0)

shared_ptr<T>是3件事。

它是指向T,删除器和引用计数的指针。它也是一个弱引用计数(即一半)。

Deleter告诉shared_ptr<T>当引用计数达到0时要做什么。从某种意义上说,它与指向T的指针无关:你可以使用我称之为&#34的指针;上帝模式&#34;共享指针构造函数以完全离开它们 - shared_ptr<T>::shared_ptr( shared_ptr<U>, T* ) - 并从shared_ptr<U>获取引用计数,并从T*获取指针。

Deleter绑定的点正在构建:两种最常见的方式是通过shared_ptr<T>::shared_ptr(T*)或通过make_shared<T>。此时,引用计数返回0时发生的情况是固定的。

您可以将shared_ptr<T>复制到shared_ptr<Base>,然后删除。你可以&#34;上帝模式&#34;窃取引用计数,并将指向成员变量的指针传递给指向的类型:原始的Deleter跟随。

shared_ptr<T>达到0引用计数时,它不知道它将做什么来销毁:删除器在破坏点处是一些任意任务,在此点决定建设。

所以如果&#34;如何摧毁T&#34;在智能指针创建的地方可见,你很好。相比之下,对delete ptr;的调用直接需要&#34;如何销毁T&#34;在删除时显示

这就是你得到不同行为的原因。