在以下代码中:
智能指针数据成员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
}
答案 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 实例之前不会发生(通过{{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;在删除时显示。
这就是你得到不同行为的原因。