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

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

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


person Frederick The Fool    schedule 06.11.2008    source источник
comment
@ Фредерик, ты не болван, это хороший вопрос!   -  person jwfearn    schedule 06.11.2008


Ответы (6)


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

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

class MyClass
{
public:
   // public stuff

private:
#include "MyClassPrivate.h"
};

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

person Dan Hewett    schedule 06.11.2008
comment
Но конечно! Какая я дура, чтобы задать этот вопрос. Спасибо всем в любом случае. - person Frederick The Fool; 06.11.2008
comment
Другим недостатком является то, что изменение частного интерфейса по-прежнему требует перекомпиляции клиентов. - person peterchen; 06.11.2008
comment
Почему этот ответ принят? MyClassPrivate.h так же легко читается, как и исходный заголовок. Он все еще требует перекомпиляции. Размер объекта не имеет большого значения. Настоящими преградами являются эффективность и обратная совместимость с некоторыми идиомами C. - person jfs; 06.11.2008
comment
К сожалению, MyClassPrivate.h не добавляет никакой ценности с точки зрения времени компоновки. Вам все равно придется пройти их все. Для закрытых функций-членов шаблона было бы гораздо лучше вообще не включать их в заголовок. - person Edward KMETT; 07.11.2008
comment
У меня душа болит при виде этого заявления. - person KJAWolf; 07.11.2008
comment
@Дж.Ф. Себастьян, согласен. Почему это принятый ответ и почему он так высоко оценивается, он полностью упускает суть. - person eodabash; 08.04.2011
comment
Мне не нравится оператор включения. Это злоупотребление препроцессором. - person Matt Melton; 04.03.2014
comment
Это не ответ. Он не меняет код полностью, а только разбивает один файл на два. Скомпилированный код точно такой же, как если бы элементы были помещены непосредственно в заголовочный файл. Здесь нет прыщей! - person Fabian; 30.06.2018

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

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

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

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

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

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

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

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

person orcmid    schedule 06.11.2008
comment
Это не кажется хорошей идеей. Это хакерство и создается впечатление, что вы активно боретесь с языком, а не используете его! Внезапно непрозрачные указатели в C выглядят как чистое и простое решение... - person dietr; 24.04.2013

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

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

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

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

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

#pragma pimpl(MyClass_private.hpp)

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

person Matt Cruikshank    schedule 06.11.2008
comment
Лучший ответ! Для каждого класса pimpl мне приходится снова и снова вводить один и тот же код, хотя на самом деле это довольно просто. К сожалению, сложно написать шаблон или базовый класс, чтобы сделать это только один раз. Можете проверить это (github.com/oliora/samples). Я бы предпочел общее решение, которое скрывает STL. - person Fabian; 30.06.2018

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

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

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

person hark    schedule 06.11.2008

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

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

person Luc Hermitte    schedule 06.11.2008
comment
Дело не в том, что С++ поддерживает семантику значений... Дело в том, что С++ фактически будет анализировать значение, чтобы предоставить эту семантику (что, в любом случае, является наиболее эффективным и простым подходом). - person Arafangion; 13.11.2010
comment
@Ara: Не могли бы вы уточнить термин «анализ по значению»? Я никогда не слышал об этом. - person fredoverflow; 22.11.2010
comment
@Fred: К сожалению, вы поймали мою опечатку - на самом деле я имею в виду пропуск. :) - person Arafangion; 22.11.2010
comment
@sbi: я имел в виду не ответ Люка, а комментарий Арафа. - person fredoverflow; 22.11.2010

Да, но...

Вам нужно прочитать книгу Страуструпа "Design and Evolution of C++". Это затормозило бы внедрение C++.

person Jonathan Leffler    schedule 06.11.2008