Умные указатели в C ++ - PullRequest
       81

Умные указатели в C ++

3 голосов
/ 26 апреля 2011

Скажем, у нас есть base класс и derived. Итак:

class base {
     protected:
          ~base(){
                //...
          }
     // ...
};

class derived : public base {
     // ...
};

А теперь скажите, что у нас есть этот код, использующий вышеупомянутые классы с классом интеллектуального указателя:

SmartPointer<base> bptr(new derived());
delete bptr;

Я понимаю, что это предотвратит разрезание объекта derived, вызвав деструктор derived, но как он узнает, что это сделать? Не будет ли ссылка, хранящаяся в умном указателе, иметь ссылку типа base*? Обходит ли оно какое-то иерархическое дерево, приводит ли этот указатель к derived*, а затем вызывает delete? Или есть еще кое-что, о чем я не знаю?

Реализация предположительно является поточно-ориентированной, ненавязчивой и подсчитывает ссылки.

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

Ответы [ 5 ]

7 голосов
/ 26 апреля 2011

Во-первых, в нынешнем виде код не будет работать. Деструктор base должен быть как минимум protected (или производные классы должны быть друзьями базы). Деструктор private означает, что компилятор не позволит вам написать деструктор для производных классов. Теперь, при условии, что у вас есть protected деструктор ... (Помните, если вы разрабатываете класс для расширения, предоставьте либо открытый виртуальный деструктор , либо защищенный не виртуальный !)

Все зависит от реализации SmartPointer, в частности std::shared_ptr (или расширенный аналог boost::shared_ptr) способны четко управлять этой ситуацией. Решение выполняет своего рода частичное стирание типа для целей уничтожения. По сути, интеллектуальный указатель имеет шаблонный конструктор, который принимает любой указатель, который может быть назначен указателю base, но поскольку он является шаблонным, он знает конкретный тип. В этот момент он сохраняет синтетическую функцию deleter, которая будет вызывать соответствующий деструктор.

Для простоты используйте std::function:

template <typename T>
void delete_deleter( void * p ) {
   delete static_cast<T*>(p);
}

template <typename T>
class shared_pointer {
    T * ptr;
    std::function<void(void*)> deleter;
public:
    template <typename U>
    shared_pointer( U* p, std::function<void()> d = delete_deleter<U> ) 
       : ptr(p), deleter(d)
    {}
    ~shared_pointer() {
       deleter( ptr );  // call the stored destructor
    }
};

Код предназначен только для выставок, его нужно настроить для производства (где хранить function, подсчет ссылок ...), но этого достаточно, чтобы дать вам представление: в единственной функции, где точный тип объекта известен (при создании умного указателя), вы создаете оболочку, которая будет вызывать точную версию деструктора, которая вам нужна (предоставляя некоторую нехватку стирания типа), а затем просто оставляете ее и когда вам нужно delete объект вызывает его вместо оператора delete.

Это также может использоваться для управления другими ресурсами, которые требуют вызова специального метода вместо delete:

// exhibition only!
shared_pointer<Foo> p( Factory.create(), &Factory::release );

Опять же, нужно подготовить довольно много работы, прежде чем готовить этот спектакль.

Зависимость от std::function, которая используется для упрощения стирания, может быть устранена из проблемы. В простом случае (в интеллектуальном указателе поддерживается только память, выделенная с помощью new и освобожденная с помощью delete), затем просто укажите базовый класс deleter с одним виртуальным operator()(void*), а затем выполните рефакторинг существующего delete_deleter в шаблонные производные классы из deleter, которые переопределяют operator()(void*) в текущей реализации. Если вам нужно перейти к общему случаю (держать любой тип ресурса), это не стоит усилий, просто используйте std::function или boost::function.

2 голосов
/ 26 апреля 2011

Ну, во-первых, ваш деструктор не должен быть приватным, иначе он не скомпилируется вообще. Во-вторых, если вы используете «умный указатель», вам, вероятно, вообще не следует удалять указатель вручную (хотя я не знаю, какую реализацию вы используете, но мне это кажется странным).

В любом случае, если вам интересно, как вызывается деструктор производного класса при удалении объекта через указатель на базовый класс, ответ - полиморфизм. Но вы пропускаете объявление virtual в своем деструкторе, сейчас ваш код не будет вызывать деструктор производного класса.

Как большинство реализаций C ++ реализуют это через виртуальную таблицу .

1 голос
/ 26 апреля 2011

Если вы используете какой-либо из умных указателей наддува или другой, который не является friend вашего Base класса, то этот код не будет компилироваться, потому что деструктор класса Base равен protected (который является тем же самымкак private для других независимых от Base классов).

Теперь давайте рассмотрим, что вы сделали SmartPointer<Base> другом Base.В этом случае код будет работать, но он будет вызывать не деструктор Derived, а деструктор Base, потому что здесь ваш класс Base не является полиморфным.Вы должны объявить destrucotr из Base как virtual.В последнем случае при удалении вашего умного указателя будет вызван правильный деструктор.

0 голосов
/ 26 апреля 2011

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

Запись в Википедии о виртуальных десктрукторах

0 голосов
/ 26 апреля 2011

эта программа недействительна.

1) Дтор базы приват

2) Дтор базы не является виртуальным

чтобы ответить на ваш вопрос: вам нужно исправить № 1 и № 2. затем dtor будет вызываться с использованием динамической диспетчеризации (которая будет вызывать каждый dtor в обратном порядке построения).

без внесения этих исправлений единственный способ SmartPointer узнать, как вызвать производный dtor в этом примере и определенным образом, - это если бы SmartPointer был слишком умным (или утомительным для использования).

...