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