Может ли С++ не избавиться от идиомы pimpl?
Как я понимаю, идиома pimpl существует только потому, что С++ заставляет вас помещать все частные члены класса в заголовок. Если в заголовке должен содержаться только открытый интерфейс, теоретически любое изменение в реализации класса не потребовало бы перекомпиляции для остальной части программы.
Я хочу знать, почему С++ не предназначен для такого удобства. Почему он вообще требует, чтобы частные части класса были открыто отображены в заголовке (каламбур не предназначен)?
Ответы
Ответ 1
Это связано с размером объекта. Файл h используется, среди прочего, для определения размера объекта. Если частные члены не указаны в нем, то вы не знаете, насколько большой объект для нового.
Вы можете имитировать, однако, свое желаемое поведение следующим образом:
class MyClass
{
public:
// public stuff
private:
#include "MyClassPrivate.h"
};
Это не влияет на поведение, но оно получает личные данные из файла .h.
С другой стороны, это добавляет еще один файл для поддержки.
Кроме того, в визуальной студии intellisense не работает для частных членов - это может быть плюс или минус.
Ответ 2
Я думаю, что здесь есть путаница. Проблема не в заголовках. Заголовки ничего не делают (это просто способы включить общие биты исходного текста из нескольких файлов исходного кода).
Проблема, так же как и одна, состоит в том, что объявления классов в С++ должны определять все, общедоступное и частное, что должен иметь экземпляр для работы. (То же самое относится и к Java, но способ ссылки на работы с внешними компиляторами делает ненужным использование чего-либо типа общих заголовков.)
В природе обычных объектно-ориентированных технологий (а не только на С++) кто-то должен знать конкретный класс, который используется, и как использовать его конструктор для реализации реализации, даже если вы используете только общественные части. Устройство в (3, ниже) скрывает его. Практика в (1, ниже) разделяет проблемы, независимо от того, выполняете ли вы (3) или нет.
-
Используйте абстрактные классы, которые определяют только общие части, в основном методы, и пусть класс реализации наследуется от этого абстрактного класса. Итак, используя обычное соглашение для заголовков, существует абстрактный .hpp, который используется совместно. Существует также реализация .hpp, которая объявляет унаследованный класс и передается только модулям, реализующим методы реализации. Файл implementation.hpp будет #include "abstract.hpp" для использования в объявлении класса, который он делает, так что существует одна точка обслуживания для объявления абстрактного интерфейса.
-
Теперь, если вы хотите принудительно скрывать декларацию класса реализации, вам нужно иметь некоторый способ запроса на создание конкретного экземпляра без обладания конкретным полным объявлением класса: вы не можете использовать новые, а вы не могут использовать локальные экземпляры. (Вы можете удалить его.) Введение вспомогательных функций (включая методы для других классов, которые предоставляют ссылки на экземпляры классов) является заменой.
-
Наряду с частью файла заголовка, который используется как общее определение абстрактного класса/интерфейса, он включает в себя сигнатуры функций для внешних вспомогательных функций. Эти функции должны быть реализованы в модулях, которые являются частью конкретных реализаций класса (поэтому они видят полное объявление класса и могут использовать конструктор). Подпись вспомогательной функции, вероятно, очень похожа на подпись конструктора, но в результате она возвращает ссылку на экземпляр (этот прокси-конструктор может возвращать указатель NULL и даже может генерировать исключения, если вам нравится такая вещь). Вспомогательная функция создает конкретный экземпляр реализации и возвращает его в качестве ссылки на экземпляр абстрактного класса.
Миссия выполнена.
О, а перекомпиляция и повторная компоновка должны работать так, как вы хотите, избегая перекомпиляции вызывающих модулей, когда изменяется только реализация (поскольку вызывающий модуль больше не выполняет никаких распределений памяти для реализаций).
Ответ 3
Вы все игнорируете точку вопроса -
Почему разработчик должен указать код PIMPL?
Для меня лучший ответ, который я могу придумать, заключается в том, что у нас нет хорошего способа выразить код на С++, который позволяет вам работать на нем. Например, отражение времени компиляции (или предпроцессорное или другое) или код DOM.
С++ плохо требует, чтобы один или оба из них были доступны разработчику для метапрограммирования.
Тогда вы могли бы написать что-то подобное в своем общедоступном MyClass.h:
#pragma pimpl(MyClass_private.hpp)
И затем напишите свой собственный, действительно довольно тривиальный генератор оболочки.
Ответ 4
У кого-то будет гораздо более подробный ответ, чем у меня, но быстрый ответ будет двояким: компилятор должен знать всех членов структуры, чтобы определить требования к пространству хранения, а компилятор должен знать порядок эти участники детерминированы для создания смещений.
Язык уже довольно сложный; Я думаю, что механизм разделения определений структурированных данных через код будет немного катастрофой.
Как правило, я всегда видел классы политики, используемые для определения поведения реализации в Pimpl-манере. Я думаю, что есть некоторые дополнительные преимущества использования шаблона политики - проще для обмена реализациями, можно легко объединить несколько частичных реализаций в единую единицу, что позволит вам разбить код реализации на функциональные, многоразовые единицы и т.д.
Ответ 5
Может быть, потому, что размер класса требуется при передаче его экземпляра по значениям, его агрегации в других классах и т.д.?
Если С++ не поддерживает семантику значений, это было бы хорошо, но это так.
Ответ 6
Да, но...
Вам нужно прочитать книгу "Дизайн и эволюция С++" Страуступа. Это могло бы помешать захвату С++.