Pimpl для шаблонного класса

Я хочу использовать идиому pimpl, чтобы избежать необходимости использования пользователями моей библиотеки наших внешних зависимостей (например, boost и т.д.), однако, когда мой класс шаблонизирован, это кажется невозможным, потому что методы должны быть в заголовке. Есть ли что-то, что я могу сделать вместо этого?

Ответ 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.,