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

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

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

6 ответов

Решение

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

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

class MyClass
{
public:
   // public stuff

private:
#include "MyClassPrivate.h"
};

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

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

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

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

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

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

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

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

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

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

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

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

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

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

#pragma pimpl(MyClass_private.hpp)

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

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

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

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

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

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

Да, но...

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

Другие вопросы по тегам