Избегайте нарезки объектов для невиртуальных деструкторов

Я пишу код для интеллектуальных указателей в качестве упражнения. Используя онлайн-учебники (1 , 2) Я разработал обычный класс интеллектуальных указателей с подсчетом ссылок. Проблема в том, что я не могу понять следующее:

когда интеллектуальный указатель обнаруживает, что больше не существует ссылок на конкретный объект, он должен удалить объект с помощью указателя на исходный тип, даже если аргумент шаблона конечного интеллектуального указателя имеет базовый тип. Это делается для того, чтобы избежать нарезки объектов для невиртуальных деструкторов.

Как я могу этого добиться. В основном мой код выглядит следующим образом (из учебника).

template < typename T > class SP
{
private:
    T*    pData;       // pointer
    RC* reference; // Reference count

public:
    SP() : pData(0), reference(0) 
    {
        // Create a new reference 
        reference = new RC();
        // Increment the reference count
        reference->AddRef();
    }

    SP(T* pValue) : pData(pValue), reference(0)
    {
        // Create a new reference 
        reference = new RC();
        // Increment the reference count
        reference->AddRef();
    }

    SP(const SP<T>& sp) : pData(sp.pData), reference(sp.reference)
    {
        // Copy constructor
        // Copy the data and reference pointer
        // and increment the reference count
        reference->AddRef();
    }

    ~SP()
    {
        // Destructor
        // Decrement the reference count
        // if reference become zero delete the data
        if(reference->Release() == 0)
        {
            delete pData;
            delete reference;
        }
    }

    T& operator* ()
    {
        return *pData;
    }

    T* operator-> ()
    {
        return pData;
    }
    
    SP<T>& operator = (const SP<T>& sp)
    {
        // Assignment operator
        if (this != &sp) // Avoid self assignment
        {
            // Decrement the old reference count
            // if reference become zero delete the old data
            if(reference->Release() == 0)
            {
                delete pData;
                delete reference;
            }

            // Copy the data and reference pointer
            // and increment the reference count
            pData = sp.pData;
            reference = sp.reference;
            reference->AddRef();
        }
        return *this;
    }
};

ИЗМЕНИТЬ:

Для этого мне нужен указатель на исходный тип.

Я разместил здесь вопрос: удалить с помощью указателя на Derived, а не на Base

Но теперь, после просмотра комментариев и ответов, я думаю, что оба связаны. У меня есть конструктор:

template <typename T>
template <typename U>
Sptr<T>::Sptr(U* u) : obj(u),ref(NULL) {
    //do something
    ref = new RC();
    ref->AddRef();
}

Теперь рассмотрим Sptr<Base1> sp(new Derived);, где Derived происходит от Base1. Base1 имеет защищенный конструктор/деструктор. Который хранится для объекта типа T Но мне нужно сохранить его через объект типа U. Мне нужно сохранить это. Как я могу это сделать?


person footy    schedule 08.04.2013    source источник
comment
Если базовый класс не имеет виртуального деструктора, и кто-то пытается удалить производный класс через указатель на этот базовый класс, значит, кто-то делает это неправильно.   -  person Chad    schedule 08.04.2013
comment
Чтобы достичь этого, вам нужно предоставить SP конструктор шаблона SP<T>::SP(U *u) { ... } и каким-то образом сохранить исходный тип U (который должен быть производным от T), чтобы иметь возможность вызывать деструктор U позже.   -  person Angew is no longer proud of SO    schedule 08.04.2013
comment
Указывает ли С++ 11, что совместимый интеллектуальный указатель должен делать это? Похоже, что std::unique_ptr не соответствует: ideone.com/iyanmY   -  person Chad    schedule 08.04.2013
comment
@Chad Нет. Только shared_ptr должен (и делает) — но AFAIR даже тогда, только когда вы не инициализируете его с помощью make_shared.   -  person Konrad Rudolph    schedule 08.04.2013
comment
Вам также, кажется, не хватает операторов копирования, перемещения и присваивания, чтобы позволить SmartPtr<Base> = SmartPtr<Derived> работать.   -  person Yakk - Adam Nevraumont    schedule 08.04.2013
comment
@Angew Как я могу сохранить тип U? У меня есть этот конструктор. Я обновлю вопрос.   -  person footy    schedule 09.04.2013
comment
@footy Это показано в ответе Якка; он хранит выражение delete, напечатанное на исходном U*.   -  person Angew is no longer proud of SO    schedule 09.04.2013


Ответы (2)


Вашему умному указателю нужны 3 блока информации.

Во-первых, указатель на данные (T* или что-то в этом роде).

Во-вторых, ваш счетчик ссылок: std::atomic<int> или что-то в этом роде.

В-третьих, ваша функция разрушения (std::function<void(T*)> или что-то в этом роде).

Когда интеллектуальный указатель создается впервые, создается эта функция уничтожения. Когда ваш интеллектуальный указатель копируется в другой интеллектуальный указатель, эта функция уничтожения копируется. Если тип нового интеллектуального указателя не совпадает со старым, эта функция уничтожения оборачивается совместимым с типом способом (работает ли std::function<void(Base*)> = std::function<void(Derived*)> из коробки? Независимо от того, вы в основном делаете это).

По умолчанию эта функция уничтожения просто delete t, но в качестве дополнительного преимущества это позволяет пользователям вашего интеллектуального указателя передавать функцию уничтожения, которая не всегда delete t.

Забавно, но на эквиваленте reset вы заменяете свою функцию уничтожения. Таким образом, вы могли бы сделать сигнатуру функции уничтожения std::function<void()>, что немного упрощает ее перемещение между интеллектуальными указателями типа T и U.

template < typename T > class SP
{
private:
  T*    pData;       // pointer
  RC* reference; // Reference count
  std::function<void()> destroyData;
public:
  template<typename U>
  SP(U* pValue):
    pData(pValue),
    reference(nullptr),
    // store how to destroy pValue now, for later execution:
    destroyData([pValue]()->void{
      delete pValue;
    })
  {
    // Create a new reference 
    reference = new RC();
    // Increment the reference count
    reference->AddRef();
  }
  // similar for operator=, and you may have to do something for SP<T> as well:
  template<typename U>
  SP(const SP<U>& sp):
    pData(sp.pData),
    reference(sp.reference),
    destroyData(sp.destroyData)
  {
    // Copy constructor
    // Copy the data and reference pointer
    // and increment the reference count
    reference->AddRef();
  }
  template<typename U>
  SP<T>& operator = (const SP<U>& sp)
  {
    // blah blah blah, then
    destroyData = sp.destroyData;
  }

  ~SP()
  {
    // Destructor
    // Decrement the reference count
    // if reference become zero delete the data
    if(reference->Release() == 0)
    {
        delete reference;
        destroyData(); // here I destroyed it!
    }
  }
};

или что-то вроде того

person Yakk - Adam Nevraumont    schedule 08.04.2013
comment
как я могу сохранить указатель на правильную функцию уничтожения. Не могли бы вы быть немного более конкретным, так как я новичок в этом стиле кодирования шаблонов. - person footy; 09.04.2013
comment
Включен @footy фрагмент кода, демонстрирующий технику. Я также включил начало работы с указателями, отличными от T*, для построения, и типами, отличными от SP<T>, назначенными SP<T>. Он не завершен, но я надеюсь, что вы поняли идею. Вы можете подумать о том, чтобы в конечном итоге использовать SFINAE, чтобы сделать действительными U типы только те, которые происходят от T в точке разрешения перегрузки, но это продвинутый метод. - person Yakk - Adam Nevraumont; 09.04.2013
comment
Спасибо, это спасло мне жизнь :P - person footy; 10.04.2013

Альтернативный подход предполагает делегирование удаления другому классу.

// non templated base
class DeleterBase {
    public:
        virtual ~DeleterBase() { };
};

template <typename T>
class Deleter : public DeleterBase {
    private:
        T *ptr;
    public:
        Deleter(T *p) // remember the pointer with the correct type here
            : ptr{p}
        { }

        ~Deleter() { 
            delete this->ptr; // invokes correct destructor
        }
};

now in the smart pointer:

template <typename T>
class SP {
    private:
        T *p;
        DeleterBase *deleter;
    public:
        template <typename U> // U is deduced to actual type
        explicit SP(U *p)
            : p{p},
            deleter{new Deleter<U>(p)} // correct type
        { }

        // transfer control in the move constructor/assignment
        // increase the ref count in copy constructor/assignment

        // now just delete the deleter in the dtor
        ~SP() {
            if (reference_count_is_zero()) { // however you implement this
                delete this->deleter;
            }
        }
};

Поскольку у вас уже есть класс RC, вы можете перенести показанные здесь функции в этот класс. Сделайте этот класс шаблонным с нешаблонной базой и уничтожьте указатель, когда его счетчик ссылок будет удален. Одной вещью меньше.

person Ryan Haining    schedule 15.04.2014