Решение GotW # 101 "фактически разрешает что-либо?

Сначала прочитайте сообщения Herb Sutters GotW, касающиеся pimpl в С++ 11:

У меня возникли проблемы с пониманием решения, предложенного в GotW # 101. Насколько я понимаю, все проблемы, которые были решены в GotW # 100, вернулись с удвоенной силой:

  • Элементы pimpl представляют собой шаблоны вне очереди, и определения не видны в месте использования (в определении class widget класса и неявно сгенерированные специальные функции-члены из widget). Также нет никаких явных инстанций. Это приведет к нерешенным внешним ошибкам при связывании.

  • widget::impl по-прежнему не завершен в точке, где pimpl<widget::impl>::~pimpl() задан (я не думаю, что он на самом деле был экземпляр вообще, просто ссылки). Поэтому std::unique_ptr<widget::impl>::~unique_ptr() вызывает delete указатель на неполный тип, который создает поведение undefined, если widget::impl имеет нетривиальный деструктор.

Пожалуйста, объясните, что заставляет компилятор генерировать специальные элементы в контексте, где widget::impl завершено. Потому что я не вижу, как это работает.


Если GotW # 101 по-прежнему требует явного определения widget::~widget() в файле реализации, где widget::impl завершено, пожалуйста, объясните комментарий "Более прочный" (который он процитировал в своем ответе).

Я смотрю на основную претензию GotW # 101, что оболочка "исключает некоторые части шаблона", которая мне кажется (на основе остальной части абзаца) означает объявление widget::~widget() и определение. Поэтому, пожалуйста, не полагайтесь на это в своем ответе, в GotW # 101, который ушел!


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

Ответ 1

Вы правы; в примере, похоже, отсутствует явный шаблонный экземпляр. Когда я пытаюсь запустить пример с конструктором и деструктором для widget::impl в MSVC 2010 SP1, я получаю ошибку компоновщика для pimpl<widget::impl>::pimpl() и pimpl<widget::impl>::~pimpl(). Когда я добавляю template class pimpl<widget::impl>;, он отлично связывается.

Другими словами, GotW # 101 устраняет все шаблоны с GotW # 100, но вам нужно добавить явное создание шаблона pimpl<...> с реализацией pimpl impl. По крайней мере, с № 101 плита котла, которая вам нужна, проста.

Ответ 2

Я думаю, что путаница такова: оболочка pimpl может быть шаблоном, класс виджетов не является:

demo.h

#include "pimpl_h.h"

// in header file
class widget {
public:
    widget();
    ~widget();
private:
    class impl;
    pimpl<impl> pimpl_;
};

demo.cpp

#include "demo.h"
#include "pimpl_impl.h"

// in implementation file
class widget::impl {
    // :::
};

widget::widget() : pimpl_() { }
widget::~widget() { } // or =default

Как вы можете видеть, никто никогда не увидит конструктор templated для класса виджета. Это будет только одно определение, и нет необходимости в "явной инстанцировании".

И наоборот, деструктор ~pimpl<> создается только из единственной точки определения деструктора ~widget(). В этот момент класс impl по определению является полным.

Ошибок связи и нарушений ODR/UB нет.

Бонусы/дополнительные преимущества

Как сам Herb точно объясняет в своем посте (Почему это улучшение над ручным идиомом Pimpl? 1), существует много преимуществ использования этой оболочки pimpl, которая связана с повторным использованием кистей реализации:

  • используйте шаблон для защиты от нежелательной ходьбы от подводных камней.
  • вы получаете конструкторы переадресации, идеальные конструкторы пересылки с С++ 0x/С++ 11, не нужно мечтать о шаблоне шаблона шаблона с переменным списком вариационных аргументов с rvalue-reffed, пересылающим пакет аргументов в конструктор завершенных классов и т.д. и т.д.

Короче: DRY и удобство.


<суб > 1, чтобы процитировать (акцент мой): Суб >

  • Во-первых, код проще, поскольку он устраняет некоторые фрагменты шаблона. В ручной версии вы также должны объявить конструктор и написать его тело в файле реализации и явно выделите объект impl. Вы также должны помнить, чтобы объявить деструктор и записать его тело в файле реализации, для непонятных языковых причин, описанных в # 100.
  • Во-вторых, код более надежный. В ручной версии, если вы забыли, чтобы написать деструктор out-of-line, Pimpld класс будет компилироваться изолированно и, по-видимому, находится в состоянии проверки, но не сможет скомпилировать, когда используется вызывающим, который пытается уничтожить объект и встречается с полезным. "Невозможно создать деструктор, потому что impl, вы знаете, неполная" ошибка, которая оставляет автора вызывающего кода, царапающего его голову, когда он подходит к вашему офису, чтобы развернуть вас, чтобы проверить что-то сломанное.