Мне было интересно, что заставит программиста выбрать либо идиому Pimpl, либо чистый виртуальный класс и наследование.
Я понимаю, что идиома pimpl включает одну явную дополнительную косвенность для каждого общедоступного метода и накладные расходы на создание объекта.
С другой стороны, виртуальный класс Pure имеет неявную косвенную адресацию (vtable) для наследующей реализации, и я понимаю, что никаких накладных расходов на создание объекта нет.
РЕДАКТИРОВАТЬ : Но вам понадобится фабрика, если вы создадите объект снаружи
Что делает чистый виртуальный класс менее желательным, чем идиома pimpl?
c++
abstract-class
pimpl-idiom
Аркаитц Хименес
источник
источник
Ответы:
При написании класса C ++ уместно подумать, будет ли он
Тип значения
Копирование по ценности, идентичность никогда не важна. Это уместно, чтобы он был ключом в std :: map. Пример, класс «строка», класс «дата» или класс «комплексное число». Имеет смысл «копировать» экземпляры такого класса.
Тип сущности
Идентичность важна. Всегда передается по ссылке, а не по «значению». Часто вообще не имеет смысла «копировать» экземпляры класса. Когда это действительно имеет смысл, обычно более уместен метод полиморфного «клонирования». Примеры: класс Socket, класс базы данных, класс «политики», все, что было бы «закрытием» на функциональном языке.
Как pImpl, так и чистый абстрактный базовый класс - это методы уменьшения зависимостей времени компиляции.
Однако я всегда использую pImpl только для реализации типов значений (тип 1), и только иногда, когда я действительно хочу свести к минимуму взаимосвязь и зависимости во время компиляции. Часто это не стоит того. Как вы правильно заметили, синтаксических издержек больше, потому что вам нужно писать методы пересылки для всех общедоступных методов. Для классов типа 2 я всегда использую чистый абстрактный базовый класс со связанным заводским методом (ами).
источник
Pointer to implementation
обычно заключается в сокрытии деталей структурной реализации.Interfaces
касаются создания различных реализаций. Они действительно служат двум разным целям.источник
Идиома pimpl помогает уменьшить зависимости и время сборки, особенно в больших приложениях, и сводит к минимуму раскрытие заголовков деталей реализации вашего класса одной единице компиляции. Пользователи вашего класса даже не должны знать о существовании прыща (кроме как загадочного указателя, к которому они не имеют доступа!).
Абстрактные классы (чистые виртуальные объекты) - это то, о чем ваши клиенты должны знать: если вы попытаетесь использовать их для уменьшения связи и циклических ссылок, вам нужно добавить какой-то способ, позволяющий им создавать ваши объекты (например, с помощью фабричных методов или классов, внедрение зависимости или другие механизмы).
источник
Я искал ответ на тот же вопрос. После прочтения некоторых статей и некоторой практики я предпочитаю использовать «интерфейсы чистого виртуального класса» .
Единственный недостаток ( я пытаюсь разобраться в этом ) заключается в том, что идиома pimpl может быть быстрее
источник
Ненавижу прыщи! Они делают класс некрасивым и нечитабельным. Все методы перенаправлены на прыщ. Вы никогда не увидите в заголовках, какие функции имеет класс, поэтому вы не можете его реорганизовать (например, просто изменить видимость метода). Класс чувствует себя «беременным». Я думаю, что использование iterfaces лучше и действительно достаточно, чтобы скрыть реализацию от клиента. Вы можете позволить одному классу реализовать несколько интерфейсов, чтобы они оставались тонкими. Отдавать предпочтение интерфейсам! Примечание: вам не нужен фабричный класс. Релевантно то, что клиенты класса общаются с его экземплярами через соответствующий интерфейс. Скрытие частных методов я считаю странной паранойей и не вижу для этого причин, поскольку у нас есть интерфейсы.
источник
Есть очень реальная проблема с разделяемыми библиотеками, которую идиома pimpl аккуратно обходит, чего не могут чистые виртуалы: вы не можете безопасно изменять / удалять элементы данных класса, не заставляя пользователей класса перекомпилировать свой код. Это может быть приемлемо при некоторых обстоятельствах, но не, например, для системных библиотек.
Чтобы подробно объяснить проблему, рассмотрите следующий код в вашей общей библиотеке / заголовке:
Компилятор генерирует код в разделяемой библиотеке, который вычисляет адрес целого числа, которое должно быть инициализировано, как определенное смещение (в данном случае, вероятно, ноль, потому что это единственный член) от указателя на объект A, который, как ему известно, является
this
.На стороне пользователя кода a
new A
сначала выделяетsizeof(A)
байты памяти, а затем передает указатель на эту памятьA::A()
конструктору какthis
.Если в более поздней версии вашей библиотеки вы решите отбросить целое число, сделать его больше, меньше или добавить элементы, будет несоответствие между объемом памяти, выделяемой кодом пользователя, и смещениями, которые ожидает код конструктора. Вероятный результат - сбой, если вам повезет - если вам повезет меньше, ваше программное обеспечение будет вести себя странно.
С помощью pimpl'ing вы можете безопасно добавлять и удалять элементы данных во внутренний класс, поскольку выделение памяти и вызов конструктора происходят в общей библиотеке:
Все, что вам нужно сделать сейчас, это освободить ваш открытый интерфейс от членов данных, кроме указателя на объект реализации, и вы будете защищены от этого класса ошибок.
Изменить: мне, возможно, следует добавить, что единственная причина, по которой я говорю здесь о конструкторе, заключается в том, что я не хотел предоставлять больше кода - та же аргументация применяется ко всем функциям, которые обращаются к членам данных.
источник
class A_impl *impl_;
Мы не должны забывать, что наследование - это более сильная и тесная связь, чем делегирование. Я бы также принял во внимание все вопросы, поднятые в ответах, при принятии решения, какие дизайнерские идиомы использовать при решении конкретной проблемы.
источник
Хотя это широко освещено в других ответах, возможно, я могу более подробно рассказать об одном преимуществе pimpl по сравнению с виртуальными базовыми классами:
Подход pimpl прозрачен с точки зрения пользователя, то есть вы можете, например, создавать объекты класса в стеке и использовать их непосредственно в контейнерах. Если вы попытаетесь скрыть реализацию с помощью абстрактного виртуального базового класса, вам нужно будет вернуть общий указатель на базовый класс из фабрики, что усложнит его использование. Рассмотрим следующий эквивалентный клиентский код:
источник
В моем понимании эти две вещи служат совершенно разным целям. Цель идиомы прыщей - дать вам указатель на вашу реализацию, чтобы вы могли делать такие вещи, как быстрая замена для сортировки.
Цель виртуальных классов больше связана с разрешением полиморфизма, т.е. у вас есть неизвестный указатель на объект производного типа, и когда вы вызываете функцию x, вы всегда получаете правильную функцию для любого класса, на который фактически указывает базовый указатель.
Действительно, яблоки и апельсины.
источник
Самая неприятная проблема, связанная с идиомой pimpl, заключается в том, что она чрезвычайно затрудняет поддержку и анализ существующего кода. Таким образом, используя pimpl, вы платите временем и разочарованием разработчика только для того, чтобы «уменьшить зависимости и время сборки и минимизировать раскрытие заголовков деталей реализации». Решайте сами, действительно ли оно того стоит.
В частности, проблема «времени сборки» - это проблема, которую вы можете решить с помощью более совершенного оборудования или таких инструментов, как Incredibuild (www.incredibuild.com, также уже включенный в Visual Studio 2017), таким образом не влияя на дизайн вашего программного обеспечения. Дизайн программного обеспечения, как правило, не должен зависеть от способа его создания.
источник