带有enum模板参数的模板类的工厂



假设我有

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);

实现这个函数的最优雅的方式是什么?

当我决定引入一种新颜色时,我想避免对这个函数(或它的助手)进行更改。我觉得我们在编译时有所有的信息来实现这一点,但是我有困难找到一个解决方案。

首先,你需要知道有多少种颜色:

enum class Colour
{
red,
blue,
orange,
_count, // <--
};

在知道了这个数字之后,就可以创建这个大小的函数指针数组,每个函数创建各自的类。然后使用枚举作为数组的索引,并调用该函数。

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)]();
}

模板模板需要c++ 20。如果你用函数代替外部的lambda,它应该也能在c++ 17中工作。

MSVC不喜欢内部lambda,所以如果你正在使用它,你可能也需要将它转换为函数。(GCC和Clang没有问题)

我在这里使用了unique_ptr,但是没有什么可以阻止您使用原始指针。


gcc 7.3.1无法处理此代码

现在,移植到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* createColourPencil(Colour c)
{
switch (c)
{
case Colour::red:
return new Pencil<Colour::red> ();
break;
...

default:
break;
}
}

你有几个很好的可能性:

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]();
}

然而,没有一个能完全满足你的要求:

我想避免对这个函数进行更改(或它的助手),当我决定引入一种新颜色。我觉得我们在编译时已经有了实现这个的所有信息

至少有两个原因:

  • 你给工厂的颜色参数在运行时是已知的,而模板参数必须是编译时已知的
  • 没有内置的解决方案来枚举或遍历所有枚举值

没有很多解决方案可以违背第一点,除了尽可能避免使用模板。在您的案例中,是否有使用模板的好理由?你真的需要使用一个完全不同的类吗?难道不能把模板改成构造函数中传递的普通形参或类成员吗?

如果你没有选择使用模板,有一种方法可以避免重复自己。你可以用一种特殊的方式使用旧的宏,通常称为x -宏。简单的例子:

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");
}
}

这将重写第一个示例中的开关。您也可以更改宏来重写映射。但是,正如您所看到的,它可能并不比显式的可读性或可维护性更好。

相关内容

  • 没有找到相关文章

最新更新