Скрытие части реализации мэйкера для шаблонного класса
Имеется некая библиотека, реализующая шаблон TDerived
основанный на интерфейсе IBase
. В ней есть метод-мэйкер MakeObj
, которой может вызвать соответствующий аргументу конструктор в run-time.
// header_1.h
#pragma once
#include <memory>
#include <array>
template<typename T>
struct IBase
{};
template <typename T, unsigned char Num_>
struct TDerived : IBase<T>
{};
template<typename T>
using FMaker = std::unique_ptr<IBase<T>>(*)();
template<typename T, unsigned char num>
std::unique_ptr<IBase<T>> MakeObj()
{
return std::unique_ptr<IBase<T>>{new TDerived<T, num>{}};
}
template<typename T, unsigned char... num_pack>
constexpr std::array<FMaker<T>, 256>
MakeLut(::std::integer_sequence<unsigned char, num_pack...>)
{
return std::array<FMaker<T>, 256>{ &MakeObj<T, num_pack>..., &MakeObj<T, 255> };
}
template<typename T>
constexpr std::array<FMaker<T>, 256> lut{ MakeLut<T>(::std::make_integer_sequence<unsigned char, 255>()) };
template<typename T>
std::unique_ptr<IBase<T>> NewObj(unsigned char num)
{
return (*lut<T>[num])();
}
Пример вызова
// main.cpp
#include "header_1.h"
int main()
{
int num = 5;
auto ptr = NewObj<int>(num);
}
Замечательно работает.
Теперь, я хочу скрыть часть реализации мэйкера. Сделать недоступными для прямого обращения некоторые вспомогательные методы. Если бы не шаблонный параметр typename T
, я бы легко вынес часть реализации в другую единицу трансляции.
// header_1.h
#pragma once
#include <memory>
#include <array>
template<typename T>
struct IBase
{};
template <typename T, unsigned char Num_>
struct TDerived : IBase<T>
{};
template<typename T>
using FMaker = std::unique_ptr<IBase<T>>(*)();
template<typename T>
extern constexpr std::array<FMaker<T>, 256> lut;
template<typename T>
std::unique_ptr<IBase<T>> NewObj(unsigned char num)
{
return (*lut<T>[num])();
}
//source_1.cpp
#include "header_2.h"
template<typename T, unsigned char num>
std::unique_ptr<IBase<T>> MakeObj()
{
return std::unique_ptr<IBase<T>>{new TDerived<T, num>{}};
}
template<typename T, unsigned char... num_pack>
constexpr std::array<FMaker<T>, 256>
MakeLut(::std::integer_sequence<unsigned char, num_pack...>)
{
return std::array<FMaker<T>, 256>{ &MakeObj<T, num_pack>..., & MakeObj<T, 255> };
}
template<typename T>
constexpr std::array<FMaker<T>, 256> lut{ MakeLut<T>(::std::make_integer_sequence<unsigned char, 255>()) };
Но так это, конечно же, не работает. Линковщик ругается
Ошибка LNK2001 неразрешенный внешний символ "class std::array<class std::unique_ptr<struct IBase,struct std::default_delete<struct IBase > > (__cdecl*)(void),256> const lut" (??$lut@H@@3V?$array@P6A?AV?$unique_ptr@U?$IBase@H@@U?$default_delete@U?$IBase@H@@@std@@@std@@XZ$0BAA@@std@@B).
Есть ли какой-нибудь способ заставить компилироваться вторую единицу трансляции зависимо от первой? Пусть будет перекомпилироваться повторно, не страшно. Или реализация функционала в заголовочных файлах для шаблонов - это приговор?
Подобные темы уже поднимались на SO. Вот, например. Но все они очень старые, и я решил, что можно и спросить хотя бы в контексте мэйкеров. Извините, если что.
Добавлено
Под "заставить компилироваться вторую единицу трансляции зависимо от первой" имеется в виду что-то типа: Если добавить в source_1.cpp
template<>
constexpr std::array<FMaker<int>, 256> lut<int>{ MakeLut<int>(::std::make_integer_sequence<unsigned char, 255>()) };
то вызов auto ptr = NewObj<int>(num);
уже работает. Может быть как-то с помощью макросов такое можно сделать? Чтобы не вручную для каждого типа.
Ответы (1 шт):
Вот вчера всю голову сломал, а сейчас написал вопрос и само в голову решение пришло )))
Сделать мэйкер частью реализации базового класса, и в нём уже скрыть вспомогательные методы
// header_1.h
#pragma once
#include <memory>
#include <array>
template<typename T>
struct IBase;
template <typename T, unsigned char Num_>
struct TDerived : IBase<T>
{};
template<typename T>
struct IBase
{
private:
using FMaker = std::unique_ptr<IBase>(*)();
template<unsigned char num>
static std::unique_ptr<IBase<T>> MakeObj()
{
return std::unique_ptr<IBase<T>>{new TDerived<T, num>{}};
}
template<unsigned char... num_pack>
static constexpr std::array<FMaker, 256>
MakeLut(::std::integer_sequence<unsigned char, num_pack...>)
{
return std::array<FMaker, 256>{ &MakeObj<num_pack>..., & MakeObj<255> };
}
static constexpr std::array<FMaker, 256> lut{ MakeLut(::std::make_integer_sequence<unsigned char, 255>()) };
public:
static std::unique_ptr<IBase<T>> GetNew(unsigned char num)
{
return (*lut[num])();
}
};
Теперь обратиться к MakeObj
, MakeLut
и lut
нельзя. Только через GetNew
// main.cpp
#include "header_1.h"
int main()
{
int num = 5;
auto ptr = IBase<int>::GetNew(num);
//IBase<int>::MakeObj<5>(); // Error
}
Но может у кого-то будет идея получше ?