Мог ли C ++ не устранить идиому pimpl? - PullRequest
19 голосов
/ 06 ноября 2008

Как я понимаю, идиома pimpl существует только потому, что C ++ заставляет вас помещать все закрытые члены класса в заголовок. Если бы заголовок содержал только открытый интерфейс, теоретически, любое изменение в реализации класса не требовало бы перекомпиляции для остальной части программы.

Что я хочу знать, так это то, почему C ++ не предназначен для такого удобства. Почему вообще требуется, чтобы приватные части класса открыто отображались в заголовке (без каламбура)?

Ответы [ 6 ]

9 голосов
/ 06 ноября 2008

Я думаю, здесь есть путаница. Проблема не в заголовках. Заголовки ничего не делают (это всего лишь способы включить общие биты исходного текста в несколько файлов исходного кода).

Проблема, так же как и проблема, состоит в том, что объявления классов в C ++ должны определять все, публичное и приватное, что должно иметь экземпляр для работы. (То же самое относится и к Java, но из-за того, что ссылки на классы, скомпилированные извне, работают, нет необходимости использовать что-либо вроде общих заголовков.)

В сущности общих объектно-ориентированных технологий (не только C ++) необходимо, чтобы кто-то знал конкретный класс, который используется, и как использовать его конструктор для реализации, даже если вы используете только общественные части. Устройство в (3, ниже) скрывает это. Практика в (1, ниже) разделяет проблемы, независимо от того, делаете ли вы (3) или нет.

  1. Используйте абстрактные классы, которые определяют только открытые части, в основном методы, и пусть класс реализации наследуется от этого абстрактного класса. Таким образом, используя обычное соглашение для заголовков, существует abstract.hpp, который используется совместно. Существует также реализация .hpp, которая объявляет унаследованный класс и передается только тем модулям, которые реализуют методы реализации. В файле реализаций .hpp будет #include «abstract.hpp» для использования в объявлении класса, которое он делает, так что существует единственная точка обслуживания для объявления абстрактного интерфейса.

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

  3. Наряду с или как часть заголовочного файла, который используется в качестве общего определения для абстрактного класса / интерфейса, включите сигнатуры функций для внешних вспомогательных функций. Эти функции должны быть реализованы в модулях, которые являются частью конкретных реализаций классов (чтобы они видели полное объявление класса и могли использовать конструктор). Сигнатура вспомогательной функции, вероятно, очень похожа на сигнатуру конструктора, но в результате она возвращает ссылку на экземпляр (этот прокси-конструктор может возвращать указатель NULL и даже может генерировать исключения, если вам нравится такая вещь). Вспомогательная функция создает конкретный экземпляр реализации и возвращает его в качестве ссылки на экземпляр абстрактного класса.

Миссия выполнена.

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

9 голосов
/ 06 ноября 2008

Это связано с размером объекта. Файл h используется, помимо прочего, для определения размера объекта. Если приватные члены в нем не указаны, то вы бы не знали, насколько велик объект для нового.

Однако вы можете смоделировать желаемое поведение следующим образом:

class MyClass
{
public:
   // public stuff

private:
#include "MyClassPrivate.h"
};

Это не навязывает поведение, но извлекает приватные вещи из файла .h. С другой стороны, это добавляет еще один файл для поддержки. Кроме того, в visual studio intellisense не работает для частных пользователей - это может быть плюсом или минусом.

7 голосов
/ 06 ноября 2008

Вы все игнорируете суть вопроса -

Почему разработчик должен печатать код PIMPL?

Для меня лучший ответ, который я могу придумать, заключается в том, что у нас нет хорошего способа выразить код C ++, который позволял бы вам работать с ним. Например, отражение времени компиляции (или препроцессора, или чего-то еще) или код DOM.

C ++ остро нуждается в том, чтобы один или оба из них были доступны разработчику для выполнения метапрограммирования.

Тогда вы могли бы написать что-то вроде этого в своем публичном MyClass.h:

#pragma pimpl(MyClass_private.hpp)

А затем напишите свой собственный, действительно довольно тривиальный генератор обёрток.

6 голосов
/ 06 ноября 2008

У кого-то будет гораздо более подробный ответ, чем у меня, но быстрый ответ будет двояким: компилятор должен знать всех членов структуры, чтобы определить требования к пространству хранения, а компилятор должен знать порядок эти члены для генерации смещений детерминированным способом.

Язык уже довольно сложный; Я думаю, что механизм разделения определений структурированных данных по коду был бы бедствием.

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

3 голосов
/ 06 ноября 2008

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

Если бы C ++ не поддерживал семантику значений, это было бы хорошо, но это так.

2 голосов
/ 06 ноября 2008

Да, но ...

Вам нужно прочитать книгу Страуструпа "Дизайн и эволюция C ++". Это бы сдерживало поглощение C ++.

...