Предположим, я пишу статическую библиотеку. Пусть у него есть класс Foo
// mylib.h
#include <dependency_header_from_other_static_library.h>
class Foo {
// ...
private:
type_from_dependent_library x;
}
Как вы можете видеть эту библиотеку (пусть называют ее mylib
) зависит от другой библиотеки. Хорошо компилируется. Но когда пользователь компилирует это код (который использует Foo
и включает в себя mylib.h
) и связывание с моей библиотекой компиляция не удалась, потому что пользователь должен иметь dependency_header_from_other_static_library.h
Заголовочный файл для компиляции кода.
Я хочу скрыть эту зависимость от пользователя. Как это можно сделать? Одна вещь, которая приходит на ум, это PIMPL
идиома. Подобно:
// mylib.h
#include <dependency_header_from_other_static_library.h>
class Foo {
// ...
private:
class FooImpl;
boost::shared_ptr<FooImpl> impl_;
}
// mylib_priv.h
class FooImpl {
// ...
private:
type_from_dependent_library x;
}
Но это требует от меня дублирования интерфейса класса Foo
в FooImpl
, И это излишне использовать PIMPL
в моем случае?
Благодарю.
При отделении заголовка от других заголовков вы можете использовать несколько подходов:
Если используемая библиотека дает обещание о том, как она объявляет свои типы, вы можете переслать объявление необходимых типов в вашем заголовке. Конечно, это все еще означает, что вы можете ссылаться на эти типы только как указатели или как сигнатуры функций в заголовке, но это может быть достаточно хорошо. Например, если используемая библиотека обещает иметь class LibraryType
что вам нужно использовать, вы можете сделать что-то вроде этого:
// Foo.h
class LibraryType;
class Foo {
// ...
LibraryType* data;
};
Это может привести к тому, что вы не сможете использовать тип, не включая его заголовок и не перепрыгивая через подход PImpl.
Если библиотека не дает обещаний о том, как она объявляет, она может использовать void*
ссылаться на соответствующие типы. Конечно, это означает, что всякий раз, когда вы получаете доступ к данным в вашей реализации, вам необходимо void*
в соответствующий тип. Поскольку тип статически известен, используя static_cast<LibraryType*>
отлично, то есть нет накладных расходов из-за актерского состава, но это все же относительно больно.
Другой альтернативой, конечно же, является использование идиомы PImpl. Если тип предоставляет какую-либо разумную услугу, он, вероятно, немного изменит интерфейс, и это не должно сильно повлиять на репликацию интерфейса между самим классом и объявленным в частном порядке типом. Также обратите внимание, что закрытый тип — это просто контейнер данных, то есть разумно сделать его struct
и не имеют защиты от его доступа. Единственная реальная проблема заключается в том, что вам нужно убедиться, что определение типа видно в том месте, где вызывается деструктор. С помощью std::shared_ptr<T>(new T(/*...*))
устраивает для этого.
По сути, все три подхода делают одно и то же, хотя и с несколько разными методами: они предоставляют непрозрачный дескриптор, который будет использоваться в заголовочном файле, определение которого известно только для реализации. Таким образом, клиенту библиотеки не нужно включать соответствующие заголовочные файлы. Однако, если символы не разрешаются при сборке библиотеки, клиенту все равно необходимо иметь доступ к используемой библиотеке.
Других решений пока нет …