In the following code:
smart pointer data member pImpl(class Impl) and raw pointer pc(class CAT) all are incomplete data type, there is no definition of these two classes in 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;
}
//output
Impl
CAT
~Impl
For the raw pointer data member, its destuctor is not called when widget object is destructed.
While the shared_ptr
data member, its destructor has been correctly called.
To my understanding, in Widget::~Widget() it should generate some code automatically as the following:
~Widget() {
delete pc; // wrote by me
// generated by compiler
delete pImpl->get();
}
Why do shared_ptr data member and raw data member have different behavior when the widget gets destructed?
I test the code using g++4.8.2 in Linux
================================EDIT=============================== According to the answers, the reason is because of :
the code generated by compiler is NOT:
~Widget() {
delete pc; // wrote by me
// generated by compiler
delete pImpl->get();
}
it maybe something like:
~Widget() {
delete pc; // wrote by me
// generated by compiler
pimpl.deleter(); //deleter will be initailized while pimpl object is initialized
}
Because you forward declaration "CAT" in the header file, you have an incomplete data type. With this information the compiler fall into undefined behavior and the destructor may not be called.
What the standard says
if the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the behavior is undefined.
Here you can find a detailed explanation: Why, really, deleting an incomplete type is undefined behaviour? Just moving the struct declaration to before the class definition should fix your problem:
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
};
And the output
Impl
CAT
~CAT
~Impl
Forward declarations are good to speed up compilation time, but can lead to problems when more information about the data type is needed.
But why does it work for smart pointers? Here's a better explanation: Deletion of pointer to incomplete type and smart pointers
Basically, the shared_ptr only needs the declaration when it initializes or resets the pointer. That means it doesn't need the complete type on the moment of the declaration.
This functionality isn't free: shared_ptr has to create and store a pointer to the deleter functor; typically this is done by storing the deleter as part of the block that stores the strong and weak reference counts or by having a pointer as part of that block that points to the deleter (since you can provide your own deleter).
You are trying to delete an object of incomplete type, this is undefined behavior if the object type being deleted has a non-trivial destructor.
More about the matter can be read about in [expr.delete]
in the Standard, as well as under the following link:
Note: The destructor of Widget::Cat
is non-trivial since it is user-declared; in turn this means it is not called.
To fix the problem simply provide the definition of Widget::Cat
so that it's not incomplete when you do delete pc
.
The reason it works when using a shared_ptr is that the "point of deletion" doesn't happen until you actually construct the shared_ptr instance (through make_shared
; ie. when the Deleter is actually instantiated.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With