Почему неполный тип элемента данных интеллектуального указателя и элемента данных с выраженным указателем имеет другое поведение, когда их родительский деструктор?

В следующем коде:

элемент данных интеллектуального указателя 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" быть видимым в точке удаления.

И вот почему вы получаете различное поведение.