Решение 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, вы знаете, неполная" ошибка, которая оставляет автора вызывающего кода, царапающего его голову, когда он подходит к вашему офису, чтобы развернуть вас, чтобы проверить что-то сломанное.