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

cam*_*ino 10 c++ smart-pointers c++11

在以下代码中:

智能指针数据成员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
Run Code Online (Sandbox Code Playgroud)

//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)
{} 
Run Code Online (Sandbox Code Playgroud)

//main.cpp

#include "widget.h"
int main()
{
    Widget w;
}
Run Code Online (Sandbox Code Playgroud)

//输出

IMPL

〜默认地将Impl

对于原始指针数据成员,在销毁窗口小部件对象时不会调用其destuctor .

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

据我所知,在Widget :: ~Widget()中,它应该自动生成一些代码,如下所示:

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

            // generated by compiler
            delete pImpl->get();
        }
Run Code Online (Sandbox Code Playgroud)

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

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

================================编辑================= ==============根据答案,原因是因为:

编译器生成的代码不是:

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

            // generated by compiler
            delete pImpl->get();
        }
Run Code Online (Sandbox Code Playgroud)

它可能是这样的:

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

            // generated by compiler
            pimpl.deleter(); //deleter will be initailized while pimpl object is initialized
        }
Run Code Online (Sandbox Code Playgroud)

dan*_*nca 13

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

标准说的是什么

如果被删除的对象在删除时具有不完整的类类型,并且完整的类具有非平凡的析构函数或释放函数,则行为是未定义的.

在这里你可以找到一个详细的解释:为什么,真的,删除一个不完整的类型是未定义的行为? 只需将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
};
Run Code Online (Sandbox Code Playgroud)

和输出

Impl
CAT
~CAT
~Impl
Run Code Online (Sandbox Code Playgroud)

前向声明可以加快编译时间,但是当需要有关数据类型的更多信息时可能会导致问题.

但为什么它适用于智能指针?这是一个更好的解释:删除指向不完整类型和智能指针的指针

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

此功能不是免费的:shared_ptr必须创建并存储指向删除器仿函数的指针; 通常这是通过将删除器存储为存储强弱引用计数的块的一部分,或者通过将指针作为指向删除器的块的一部分来存储(因为您可以提供自己的删除器).


Fil*_*efp 5

说明

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

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


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



要解决问题,只需提供定义,Widget::Cat以便在执行时不会完整delete pc.



为什么它适用于shared_ptr

它在使用shared_ptr时的工作原理是,在实际构造shared_ptr实例之前,"删除点"不会发生(通过;即,实际上实例化了Deleter).make_shared