Подтвердить что ты не робот

Может ли С++ не избавиться от идиомы pimpl?

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

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

4b9b3361

Ответ 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

Да, но...

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