具有枚举模板参数的模板 class 的工厂
Factory for a template class with enum template parameter
假设我有
enum class Colour
{
red,
blue,
orange
};
class PencilBase
{
public:
virtual void paint() = 0;
};
template <Colour c>
class Pencil : public PencilBase
{
void paint() override
{
// use c
}
};
现在我想要一些工厂功能来创建画家
PencilBase* createColourPencil(Colour c);
实现此功能最优雅的方法是什么?
当我决定引入一种新颜色时,我想避免更改此函数(或其助手)。
我觉得我们在编译时拥有实现此目的的所有信息,但是我很难找到解决方案。
我不认为有数百万的解决方案不是吗?
PencilBase* createColourPencil(Colour c)
{
switch (c)
{
case Colour::red:
return new Pencil<Colour::red> ();
break;
...
default:
break;
}
}
首先你要知道有多少种颜色:
enum class Colour
{
red,
blue,
orange,
_count, // <--
};
知道数字后,您可以创建一个此大小的函数指针数组,每个函数创建各自的 class。然后使用枚举作为数组的索引,并调用函数。
std::unique_ptr<PencilBase> createColourPencil(Colour c)
{
if (c < Colour{} || c >= Colour::_count)
throw std::runtime_error("Invalid color enum.");
static constexpr auto funcs = []<std::size_t ...I>(std::index_sequence<I...>)
{
return std::array{+[]() -> std::unique_ptr<PencilBase>
{
return std::make_unique<Pencil<Colour(I)>>();
}...};
}(std::make_index_sequence<std::size_t(Colour::_count)>{});
return funcs[std::size_t(c)]();
}
模板 lambas 需要 C++20。如果用函数替换外部 lambda,它应该也适用于 C++17。
MSVC 不喜欢内部 lambda,因此如果您正在使用它,您可能还需要将其转换为函数。 (GCC 和 Clang 都没有问题。)
我在这里使用了 unique_ptr
,但没有什么能阻止您使用原始指针。
gcc 7.3.1 is not able to handle this code
在这里,移植到 GCC 7.3:
template <Colour I>
std::unique_ptr<PencilBase> pencilFactoryFunc()
{
return std::make_unique<Pencil<I>>();
}
template <std::size_t ...I>
constexpr auto makePencilFactoryFuncs(std::index_sequence<I...>)
{
return std::array{pencilFactoryFunc<Colour(I)>...};
}
std::unique_ptr<PencilBase> createColourPencil(Colour c)
{
if (c < Colour{} || c >= Colour::_count)
throw std::runtime_error("Invalid color enum.");
static constexpr auto funcs = makePencilFactoryFuncs(std::make_index_sequence<std::size_t(Colour::_count)>{});
return funcs[std::size_t(c)]();
}
你有几个很好的可能性:
PencilBase* createPencil (Colour colour) {
switch (colour) {
case RED:
return new Pencil<RED>();
case BLUE:
return new Pencil<BLUE>();
...
default:
throw std::invalid_argument("Unsupported colour");
}
}
或者为什么不像这个更现代的构造,它的优点是可以在运行时更新:
std::unordered_map<Colour, std::function<PencilBase*()>> FACTORIES = {
{ RED, [](){ new Pencil<RED>(); } },
{ BLUE, [](){ new Pencil<BLUE>(); } },
...
};
PencilBase* createPencil (Colour colour) {
return FACTORIES[colour]();
}
不过,none完全可以满足你的要求:
I want to avoid making changes in this function (or its helpers) when I decide to introduce a new colour. I feel like we have all the information at compile time to achieve this
至少有两个原因:
- 您提供给工厂的颜色参数在运行时已知,而您的模板参数必须在编译时已知。
- 没有内置解决方案来枚举或遍历所有枚举值
与第一点相反的解决方案并不多,除了尽可能避免使用模板。
在您的情况下,是否有充分的理由使用模板?您真的需要使用完全不同的 class 吗?不能将模板更改为在构造函数中传递的普通参数或作为 class 成员吗?
如果您别无选择使用模板,有一种方法可以避免重复您自己。您可以使用很好的旧宏,并以一种通常称为 X-macro 的特殊方式使用。快速示例:
colours.hpp:
COLOUR(RED)
COLOUR(BLUE)
COLOUR(ORANGE)
Colour.hpp:
enum Colour {
#define COLOUR(C) C,
#include "colours.hpp"
#undef COLOUR
INVALID_COLOUR
};
工厂函数:
PencilBase* createPencil (Colour colour) {
switch(colour) {
#define COLOUR(C) case C: return new Pencil<C>();
#include "colours.hpp"
#undef COLOUR
default:
throw new std::invalid_argument("Invalid colour");
}
}
这将基本上重写作为第一个示例给出的开关。您也可以更改宏以重写映射。但是,如您所见,它可能并不比显式更易读或更易于维护。
假设我有
enum class Colour
{
red,
blue,
orange
};
class PencilBase
{
public:
virtual void paint() = 0;
};
template <Colour c>
class Pencil : public PencilBase
{
void paint() override
{
// use c
}
};
现在我想要一些工厂功能来创建画家
PencilBase* createColourPencil(Colour c);
实现此功能最优雅的方法是什么?
当我决定引入一种新颜色时,我想避免更改此函数(或其助手)。 我觉得我们在编译时拥有实现此目的的所有信息,但是我很难找到解决方案。
我不认为有数百万的解决方案不是吗?
PencilBase* createColourPencil(Colour c)
{
switch (c)
{
case Colour::red:
return new Pencil<Colour::red> ();
break;
...
default:
break;
}
}
首先你要知道有多少种颜色:
enum class Colour
{
red,
blue,
orange,
_count, // <--
};
知道数字后,您可以创建一个此大小的函数指针数组,每个函数创建各自的 class。然后使用枚举作为数组的索引,并调用函数。
std::unique_ptr<PencilBase> createColourPencil(Colour c)
{
if (c < Colour{} || c >= Colour::_count)
throw std::runtime_error("Invalid color enum.");
static constexpr auto funcs = []<std::size_t ...I>(std::index_sequence<I...>)
{
return std::array{+[]() -> std::unique_ptr<PencilBase>
{
return std::make_unique<Pencil<Colour(I)>>();
}...};
}(std::make_index_sequence<std::size_t(Colour::_count)>{});
return funcs[std::size_t(c)]();
}
模板 lambas 需要 C++20。如果用函数替换外部 lambda,它应该也适用于 C++17。
MSVC 不喜欢内部 lambda,因此如果您正在使用它,您可能还需要将其转换为函数。 (GCC 和 Clang 都没有问题。)
我在这里使用了 unique_ptr
,但没有什么能阻止您使用原始指针。
gcc 7.3.1 is not able to handle this code
在这里,移植到 GCC 7.3:
template <Colour I>
std::unique_ptr<PencilBase> pencilFactoryFunc()
{
return std::make_unique<Pencil<I>>();
}
template <std::size_t ...I>
constexpr auto makePencilFactoryFuncs(std::index_sequence<I...>)
{
return std::array{pencilFactoryFunc<Colour(I)>...};
}
std::unique_ptr<PencilBase> createColourPencil(Colour c)
{
if (c < Colour{} || c >= Colour::_count)
throw std::runtime_error("Invalid color enum.");
static constexpr auto funcs = makePencilFactoryFuncs(std::make_index_sequence<std::size_t(Colour::_count)>{});
return funcs[std::size_t(c)]();
}
你有几个很好的可能性:
PencilBase* createPencil (Colour colour) {
switch (colour) {
case RED:
return new Pencil<RED>();
case BLUE:
return new Pencil<BLUE>();
...
default:
throw std::invalid_argument("Unsupported colour");
}
}
或者为什么不像这个更现代的构造,它的优点是可以在运行时更新:
std::unordered_map<Colour, std::function<PencilBase*()>> FACTORIES = {
{ RED, [](){ new Pencil<RED>(); } },
{ BLUE, [](){ new Pencil<BLUE>(); } },
...
};
PencilBase* createPencil (Colour colour) {
return FACTORIES[colour]();
}
不过,none完全可以满足你的要求:
I want to avoid making changes in this function (or its helpers) when I decide to introduce a new colour. I feel like we have all the information at compile time to achieve this
至少有两个原因:
- 您提供给工厂的颜色参数在运行时已知,而您的模板参数必须在编译时已知。
- 没有内置解决方案来枚举或遍历所有枚举值
与第一点相反的解决方案并不多,除了尽可能避免使用模板。 在您的情况下,是否有充分的理由使用模板?您真的需要使用完全不同的 class 吗?不能将模板更改为在构造函数中传递的普通参数或作为 class 成员吗?
如果您别无选择使用模板,有一种方法可以避免重复您自己。您可以使用很好的旧宏,并以一种通常称为 X-macro 的特殊方式使用。快速示例:
colours.hpp:
COLOUR(RED)
COLOUR(BLUE)
COLOUR(ORANGE)
Colour.hpp:
enum Colour {
#define COLOUR(C) C,
#include "colours.hpp"
#undef COLOUR
INVALID_COLOUR
};
工厂函数:
PencilBase* createPencil (Colour colour) {
switch(colour) {
#define COLOUR(C) case C: return new Pencil<C>();
#include "colours.hpp"
#undef COLOUR
default:
throw new std::invalid_argument("Invalid colour");
}
}
这将基本上重写作为第一个示例给出的开关。您也可以更改宏以重写映射。但是,如您所见,它可能并不比显式更易读或更易于维护。