Я хочу использовать идиому pimpl, чтобы избежать необходимости использования пользователями моей библиотеки наших внешних зависимостей (например, boost и т.д.), однако, когда мой класс шаблонизирован, это кажется невозможным, потому что методы должны быть в заголовке. Есть ли что-то, что я могу сделать вместо этого?
Pimpl для шаблонного класса
Ответ 1
Если шаблон templated, ваши пользователи по существу должны скомпилировать его (и это буквально верно в наиболее широко используемых реализациях С++), и поэтому им нужны ваши внешние зависимости.
Самое простое решение - поместить основную часть вашей реализации класса в базовый класс без шаблона (или инкапсулированный объект-член какого-либо класса). Решите проблему сокрытия модуля.
И затем напишите полученный шаблон (или окружение), чтобы добавить к нему безопасность типа.
Например, предположим, что у вас есть шаблон, который обеспечивает удивительную способность выделять при первом доступе (опускает необходимый конструктор, назначение, деструктор):
template <class T>
class MyContainer
{
T *instance_;
public:
MyContainer() : instance_(0) {}
T &access()
{
if (instance_ == 0)
instance_ = new T();
return *instance_;
}
};
Если вы хотите, чтобы "логика" была разделена на базовый класс без шаблона, вам нужно было бы параметризовать поведение в режиме без шаблонов, то есть использовать виртуальные функции:
class MyBase
{
void *instance_;
virtual void *allocate() = 0;
public:
MyBase() : instance_(0) {}
void *access()
{
if (instance_ == 0)
instance_ = allocate();
return instance_;
}
};
Затем вы можете добавить понимание типа во внешнем слое:
template <class T>
class MyContainer : MyBase
{
virtual void *allocate()
{ return new T(); }
public:
T &access()
{ return *(reinterpret_cast<T *>(MyBase::access())); }
};
то есть. Вы используете виртуальные функции, чтобы позволить шаблону "заполнить" операции, зависящие от типа. Очевидно, что эта схема будет иметь смысл только в том случае, если у вас есть бизнес-логика, которая стоит усилий для скрытия.
Ответ 2
Вы можете явно создавать шаблоны в исходном файле, но это возможно только в том случае, если вы знаете, каким будет тип шаблона. В противном случае не используйте идиому pimpl для шаблонов.
Что-то вроде этого:
header.hpp:
#ifndef HEADER_HPP
#define HEADER_HPP
template< typename T >
class A
{
// constructor+methods + pimpl
};
#endif
source.cpp:
#include "header.hpp"
// implementation
// explicitly instantiate for types that will be used
template class A< int >;
template class A< float >;
// etc...
Ответ 3
Существует два общих решения:
-
в то время как интерфейс зависит от какого-либо типа
T
, он переходит к более слабо типизированной реализации (например, с использованием указателейvoid*
напрямую или с помощью стирания типа) или -
вы поддерживаете только определенное и довольно ограниченное количество типов.
Второе решение имеет значение, например. char
/wchar_t
-зависимый материал.
Первое решение было довольно распространено в первые дни шаблонов С++, поскольку в то время компиляторы не умели распознавать общности в сгенерированном машинном коде и вводили так называемый "раздувание кода". Сегодня, к большому удивлению любого новичка, который пытается это сделать, шаблонное решение часто может иметь меньший размер машинного кода, чем решение, основанное на полиморфизме времени выполнения. Конечно, YMMV.
Приветствия и hth.,