假設我有
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);
實作這個功能的最優雅的方式是什么?
當我決定引入一種新顏色時,我想避免對這個函式(或其助手)進行更改。我覺得我們在編譯時擁有所有資訊來實作這一點,但是我很難找到解決方案。
uj5u.com熱心網友回復:
首先,您需要知道有多少種顏色:
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)]();
}
模板 labas 需要 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)]();
}
uj5u.com熱心網友回復:
我不認為有數百萬個解決方案,不是嗎?
PencilBase* createColourPencil(Colour c)
{
switch (c)
{
case Colour::red:
return new Pencil<Colour::red> ();
break;
...
default:
break;
}
}
uj5u.com熱心網友回復:
你有幾個很好的可能性:
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 宏的特殊方式使用。快速示例:
顏色.hpp:
COLOUR(RED)
COLOUR(BLUE)
COLOUR(ORANGE)
顏色.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");
}
}
這將基本上重寫作為第一個示例給出的開關。您也可以更改宏以重寫映射。但是,正如您所看到的,它的可讀性或可維護性可能不如顯式。
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/346200.html
