Почему неполный тип элемента данных интеллектуального указателя и элемента данных с выраженным указателем имеет другое поведение, когда их родительский деструктор?
В следующем коде:
элемент данных интеллектуального указателя pImpl (класс Impl) и raw pointer 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
Для элемента данных с необработанным указателем его destuctor не называется, когда объект виджета разрушен.
В то время как элемент данных shared_ptr
, его деструктор был правильно вызван.
Насколько я понимаю, в Widget:: ~ Widget() он должен генерировать некоторые
кода автоматически:
~Widget() {
delete pc; // wrote by me
// generated by compiler
delete pImpl->get();
}
Почему элемент данных shared_ptr и член исходных данных имеют другое поведение при удалении виджета?
Я тестирую код, используя g++ 4.8.2 в Linux
================================ EDIT ============== =================
Согласно ответам, причина в том, что:
код, сгенерированный компилятором, НЕ:
~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
}
Ответы
Ответ 1
Поскольку вы отправляете декларацию "CAT" в файл заголовка, у вас есть неполный тип данных. С помощью этой информации компилятор попадает в поведение undefined, и деструктор не может быть вызван.
Что говорится в стандарте
если удаляемый объект имеет неполный тип класса в точке удаление, а полный класс имеет нетривиальный деструктор или функция освобождения, поведение undefined.
Здесь вы можете найти подробное объяснение: Почему, действительно, удаление неполного типа - это поведение undefined?
Просто переместите объявление структуры до определения класса, чтобы исправить вашу проблему:
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
Передовые декларации хороши для ускорения времени компиляции, но могут привести к проблемам, когда требуется больше информации о типе данных.
Но почему это работает для умных указателей?
Вот лучшее объяснение: Удаление указателя на неполный тип и интеллектуальные указатели
В принципе, shared_ptr требуется только объявление, когда оно инициализирует или сбрасывает указатель. Это означает, что в момент объявления не требуется полный тип.
Эта функция не бесплатна: shared_ptr должен создавать и хранить указатель на функтор удаления; обычно это делается путем хранения deleter как часть блока, который хранит сильную и слабую ссылку подсчитывает или имеет указатель как часть этого блока, который указывает на deleter (поскольку вы можете предоставить свой собственный дебетер).
Ответ 2
Объяснение
Вы пытаетесь удалить объект неполного типа, это поведение undefined, если удаляемый тип объекта имеет нетривиальный деструктор.
Подробнее об этом можно прочитать в [expr.delete]
в стандарте, а также по следующей ссылке:
Примечание: деструктор Widget::Cat
является нетривиальным, поскольку он объявлен пользователем; в свою очередь это означает, что он не называется.
Решение
Чтобы устранить проблему, просто укажите определение Widget::Cat
, чтобы она не была неполной, когда вы делаете delete pc
.
Почему это работает для shared_ptr?
Причина, по которой она работает при использовании shared_ptr, заключается в том, что "точка удаления" не происходит, пока вы фактически не создадите экземпляр shared_ptr (через make_shared
, т.е. когда Deleter фактически создается экземпляр.
Ответ 3
A shared_ptr<T>
составляет 3 с половиной.
Это указатель на T
, Deleter и счетчик ссылок. Это также слабый счетчик ссылок (это половина).
Deleter сообщает shared_ptr<T>
, что делать, когда счетчик ссылок достигает 0. Это, в некотором смысле, не имеет отношения к указателю на T
: вы можете использовать то, что я называю конструктором общих указателей "god mode" полностью развести их - shared_ptr<T>::shared_ptr( shared_ptr<U>, T* )
- и получить счетчик ссылок из shared_ptr<U>
и указатель из T*
.
Точка, в которой связан Делетер, находится в процессе построения: два наиболее распространенных способа - через shared_ptr<T>::shared_ptr(T*)
или через make_shared<T>
. В этот момент фиксируется то, что происходит, когда счетчик ссылок возвращается к 0.
Вы можете скопировать shared_ptr<T>
в shared_ptr<Base>
, а Deleter следует за ним. Вы можете "божественный режим" украсть счетчик ссылок и передать указатель на переменную-член как тип, на который указывает: и последует следующий Deleter.
Когда a shared_ptr<T>
достигает 0 счетчика ссылок, он не имеет понятия, что он будет делать для уничтожения: Deleter - это произвольная задача в момент уничтожения, которая была решена в точке построения.
Итак, если "как уничтожить T" было видно, где был создан умный указатель, вы в порядке. Для сравнения, вызов delete ptr;
непосредственно нуждается в том, чтобы "как уничтожить T" быть видимым в точке удаления.
И вот почему вы получаете различное поведение.