Действительно ли решение GotW #101 что-то решает?

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

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

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

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

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


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

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


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


person Ben Voigt    schedule 21.12.2011    source источник
comment
Paging Доктор @HerbSutter   -  person sehe    schedule 23.12.2011
comment
@Ben Voigt: я искал и пометил несколько других вопросов с помощью gotw. Вы нашли что-то, что я пропустил?   -  person Adrian McCarthy    schedule 28.12.2011
comment
@Ben Voigt: Тогда должна быть задержка распространения в Stack Exchange. Я добавил этот тег к нескольким другим вопросам сразу после его создания для этого. С тех пор я нашел еще один вопрос и также отметил его.   -  person Adrian McCarthy    schedule 28.12.2011
comment
Я только что прочитал статью, и у меня такие же трудности с пониманием того, как лучше - потому что это явно не так, или код неполный. Добираюсь туда через google gotw #101 не работает :)   -  person John    schedule 12.10.2012


Ответы (2)


Ты прав; в примере, похоже, отсутствует явное создание экземпляра шаблона. Когда я пытаюсь запустить пример с конструктором и деструктором для 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 нужная вам котельная плита проста.

person MSN    schedule 21.12.2011
comment
widget::~widget() необходимо вызывать деструкторы для всех подобъектов. Где создается экземпляр pimpl<widget::impl>::~pimpl(), чтобы widget::~widget() мог его вызывать? - person Ben Voigt; 22.12.2011
comment
@BenVoigt: он создается в том месте, где он используется, то есть в определении widget::~widget() в файле .cpp. - person Mike Seymour; 22.12.2011
comment
@MikeSeymour: версия GotW #100 определяет widget::~widget() в файле .cpp. GotW #101 этого не делает, он говорит, что устраняет некоторые части шаблона, и конкретно упоминает этот деструктор. - person Ben Voigt; 22.12.2011
comment
@BenVoigt, ты был прав. Я попытался скомпилировать его, чтобы убедиться, что неявное создание экземпляра pimpl не включает создание экземпляра его конструктора или деструктора при реализации widget::impl. - person MSN; 22.12.2011

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

demo.h

#include "pimpl_h.h"

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

демо.cpp

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

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

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

Как видите, никто никогда не увидит «шаблонный» конструктор для класса виджета. Будет только одно его определение, и нет необходимости в «явном воплощении».

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

Ошибок компоновки и нарушений ODR/UB нет.

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

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

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

Одним словом: DRY и удобство.


1 в кавычках (выделено мной):

  • Во-первых, код проще, потому что он устраняет некоторые части шаблона: в версии, созданной вручную, вам также нужно объявить конструктор и записать его тело в файле реализации, а также явно выделить импл объект. Вы также должны не забыть объявить деструктор и записать его тело в файл реализации по непонятным языковым причинам, описанным в #100.
  • Во-вторых, код более надежен: в накатанной вручную версии, если вы забудете написать внестрочный деструктор, класс Pimpl'd будет компилироваться изолированно и, по-видимому, будет находиться в состоянии, пригодном для проверки, но не сможет скомпилироваться, если его использует вызывающая программа, которая пытается уничтожить объект и сталкивается с полезным сообщением «не может сгенерировать деструктор, потому что импл, ну, вы знаете, неполная», из-за которой автор вызывающего кода чешет в затылке, когда идет к вам в офис, чтобы отчитать вас за то, что вы проверили что-то сломанное.
person sehe    schedule 21.12.2011
comment
Добавлены некоторые из наиболее очевидных аргументов относительно того, что решает оболочка pimpl, помимо основного вопроса ОП. - person sehe; 22.12.2011
comment
Но решение Херба не имеет нестандартного widget::~widget или нестандартного определения. Он сказал, что в версии, созданной вручную, вы должны не забыть объявить деструктор и записать его тело в файл реализации, теперь вы говорите, что вам все еще нужно сделать это для оболочки. Что противоречит утверждениям, сделанным в пуле надежности. - person Ben Voigt; 22.12.2011
comment
Я только что скопировал этот деструктор out-of-line из его кода? Теперь, если вы утверждаете, что его установка по умолчанию может привести к той же проблеме, мне придется обратиться к стандарту, но я настоятельно рекомендую, чтобы компилятор выдавал этот деструктор по умолчанию в точка, где объявлено ~widget() = default;, то есть в момент, когда impl является полным типом. - person sehe; 22.12.2011
comment
sehe, вы копируете сверху или снизу подзаголовок с пометкой Solution в GotW #101? - person Ben Voigt; 22.12.2011
comment
Хм. Подожди. Я перепутал фрагменты кода! Вы могли бы быть на что-то там. Вернусь позже - person sehe; 22.12.2011
comment
В заключение: Херб говорит, что обертка устраняет некоторые элементы шаблона. Похоже, что код не работает, если их устранить. - person Ben Voigt; 22.12.2011
comment
У вас была возможность посмотреть на это дальше? - person Ben Voigt; 23.12.2011
comment
Код, который вы цитируете, является повторением ручного решения для № 100, а не ответом для № 101. Предположительно, #101 позволяет вам пропустить явный внестрочный деструктор для виджета. Но если вы опустите его, компилятор не создаст экземпляр pimp‹impl›::~impl, что приведет к отсутствию внешней проблемы во время компоновки. - person Adrian McCarthy; 23.12.2011
comment
@AdrianMcCarthy: я думаю, вы ошиблись с именем деструктора. Я предполагаю, что вы имели в виду pimpl<widget::impl>::~pimpl(), который я упомянул в вопросе как не конкретизированный. - person Ben Voigt; 23.12.2011
comment
@BenVoigt: к сожалению, нет, завален работой и музыкальными выступлениями (в это время года). Я собираюсь согласиться с вашим выводом на данный момент, хотя кажется, что Херб пропустил это. Я не могу убедить себя, что без явного деструктора можно обойтись, как обстоят дела. Я все еще хочу проверить фактический испускаемый код, чтобы увидеть, есть ли какая-то скрытая подсказка. Что затем привело бы к очень тщательному поиску спецификаций, чтобы понять, почему это будет работать. - person sehe; 23.12.2011