具有C++模板的生成器模式
Builder Pattern with C++ templates
我有一个高度可配置的类,其中包含许多模板参数,如下所示:
template<bool OptionA = false, bool OptionB = false, bool OptionC = false, class T = Class1B>
class MyClass
{
}
现在,如果我想创建类类型并且只想将选项B设置为true,则必须执行以下操作:
MyClass<false, true>
特别是对于许多模板参数,这变得很麻烦。
不,我的问题是,是否有任何示例可用于使用构建器模式创建基于模板的类类型?
我正在寻找这样的东西:
class Builder
{
useOptionA();
useOptionB();
useOptionC();
useClass2B(); //instead of Class1B
create();
}
最后,对Builder.useOptionB().useOptionC().useClass2B.create()
的调用应返回MyClass<false, true, true, Class2B>
。这可能吗?
编辑:将类添加到模板参数列表。
正如其他人所说,做你想做的事情的最简单方法是使用 enum
而不是 Builder。 但是,如果您确实想要一个构建器,则可以尝试如下操作:
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
struct Builder_t
{
Builder_t() = default;
// ~Builder_t() { std::cout << "Builder dtor." << std::endl; }
auto useOptionA() -> Builder_t<true, OptionB, OptionC, T> { return {}; }
auto useOptionB() -> Builder_t<OptionA, true, OptionC, T> { return {}; }
auto useOptionC() -> Builder_t<OptionA, OptionB, true, T> { return {}; }
auto useClass2B() -> Builder_t<OptionA, OptionB, OptionC, Class2B> { return {}; }
MyClass<OptionA, OptionB, OptionC, T> create() { return {}; }
};
using Builder = Builder_t<>;
// ...
// Build MyClass<true, false, false, Class2B>:
auto ma2 = Builder{}.useOptionA().useClass2B().create();
这导致每个函数返回一个不同的Builder
,其模板将由下一个函数使用;最终模板用作MyClass
'模板。 每个函数修改其指定的模板参数,允许构建器模式的编译时版本。 但是,它确实有成本,如果用户定义的析构函数未注释,则成本变得很明显。
考虑这个简单的测试程序:
#include <iostream>
#include <typeinfo>
class Class1B {};
class Class2B {};
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
class MyClass
{
public:
MyClass() {
std::cout << "MyClass<"
<< OptionA << ", "
<< OptionB << ", "
<< OptionC << ", "
<< "type " << typeid(T).name() << ">"
<< std::endl;
}
};
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
struct Builder_t
{
Builder_t() = default;
// ~Builder_t() { std::cout << "Builder dtor." << std::endl; }
auto useOptionA() -> Builder_t<true, OptionB, OptionC, T> { return {}; }
auto useOptionB() -> Builder_t<OptionA, true, OptionC, T> { return {}; }
auto useOptionC() -> Builder_t<OptionA, OptionB, true, T> { return {}; }
auto useClass2B() -> Builder_t<OptionA, OptionB, OptionC, Class2B> { return {}; }
MyClass<OptionA, OptionB, OptionC, T> create() { return {}; }
};
using Builder = Builder_t<>;
int main()
{
std::cout << std::boolalpha;
std::cout << "Default:n";
std::cout << "Direct: ";
MyClass<> m;
std::cout << "Builder: ";
auto mdefault = Builder{}.create();
std::cout << std::endl;
std::cout << "Builder pattern:n";
std::cout << "A: ";
auto ma = Builder{}.useOptionA().create();
std::cout << "C: ";
auto mc = Builder{}.useOptionC().create();
std::cout << "---n";
std::cout << "AB: ";
auto mab = Builder{}.useOptionA().useOptionB().create();
std::cout << "B2: ";
auto mb2 = Builder{}.useOptionB().useClass2B().create();
std::cout << "---n";
std::cout << "ABC: ";
auto mabc = Builder{}.useOptionA().useOptionB().useOptionC().create();
std::cout << "AC2: ";
auto mac2 = Builder{}.useOptionA().useOptionC().useClass2B().create();
std::cout << "---n";
std::cout << "ABC2: ";
auto mabc2 = Builder{}.useOptionA().useOptionB().useOptionC().useClass2B().create();
}
通常,输出如下(使用 GCC(:
Default:
Direct: MyClass<false, false, false, type 7Class1B>
Builder: MyClass<false, false, false, type 7Class1B>
Builder pattern:
A: MyClass<true, false, false, type 7Class1B>
C: MyClass<false, false, true, type 7Class1B>
---
AB: MyClass<true, true, false, type 7Class1B>
B2: MyClass<false, true, false, type 7Class2B>
---
ABC: MyClass<true, true, true, type 7Class1B>
AC2: MyClass<true, false, true, type 7Class2B>
---
ABC2: MyClass<true, true, true, type 7Class2B>
但是,如果我们取消注释析构函数...
Default:
Direct: MyClass<false, false, false, type 7Class1B>
Builder: MyClass<false, false, false, type 7Class1B>
Builder dtor.
Builder pattern:
A: MyClass<true, false, false, type 7Class1B>
Builder dtor.
Builder dtor.
C: MyClass<false, false, true, type 7Class1B>
Builder dtor.
Builder dtor.
---
AB: MyClass<true, true, false, type 7Class1B>
Builder dtor.
Builder dtor.
Builder dtor.
B2: MyClass<false, true, false, type 7Class2B>
Builder dtor.
Builder dtor.
Builder dtor.
---
ABC: MyClass<true, true, true, type 7Class1B>
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
AC2: MyClass<true, false, true, type 7Class2B>
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
---
ABC2: MyClass<true, true, true, type 7Class2B>
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
Builder dtor.
前面Builder_t::create()
的每个调用都会创建一个不同的Builder_t
,所有这些调用都会在创建实例后销毁。 这可以通过Builder_t
constexpr
类来缓解,但如果有大量参数需要处理,这可能会减慢编译速度:
template<bool OptionA = false,
bool OptionB = false,
bool OptionC = false,
typename T = Class1B>
struct Builder_t
{
// Uncomment if you want to guarantee that your compiler treats Builder_t as constexpr.
// size_t CompTimeTest;
constexpr Builder_t()
// Uncomment if you want to guarantee that your compiler treats Builder_t as constexpr.
// : CompTimeTest((OptionA ? 1 : 0) +
// (OptionB ? 2 : 0) +
// (OptionC ? 4 : 0) +
// (std::is_same<T, Class2B>{} ? 8 : 0))
{}
constexpr auto useOptionA() -> Builder_t<true, OptionB, OptionC, T> { return {}; }
constexpr auto useOptionB() -> Builder_t<OptionA, true, OptionC, T> { return {}; }
constexpr auto useOptionC() -> Builder_t<OptionA, OptionB, true, T> { return {}; }
constexpr auto useClass2B() -> Builder_t<OptionA, OptionB, OptionC, Class2B> { return {}; }
constexpr MyClass<OptionA, OptionB, OptionC, T> create() { return {}; }
};
using Builder = Builder_t<>;
// ....
// Uncomment if you want to guarantee that your compiler treats Builder_t as constexpr.
// char arr[Builder{}/*.useOptionA()/*.useOptionB()/*.useOptionC()/*.useClass2B()/**/.CompTimeTest];
// std::cout << sizeof(arr) << 'n';
struct Builder
{
enum { A = 1, B = 2, C = 4 };
template<int f>
using MyClass_t = MyClass<f & A, f & B, f & C>;
template<int f>
static MyClass_t<f> create()
{ return MyClass_t<f>(); }
};
使用示例 (C++11(:
auto my_obj = Builder::create<Builder::A | Builder::B>();
或者,如果您只想更快地访问该类型,而不是create
方法:
Builder::MyClass_t<Builder::B> b;
如果opt_flag
放在类外,并且您不需要create
方法,则它甚至更短:
enum { optA = 1, optB = 2, optC = 4 };
template<int f>
using MyClass_t = MyClass<f & optA, f & optB, f & optC>;
int main()
{
MyClass_t<optA | optB> my_obj;
// ...
}
如果只有一个模板参数是typename
,则使用相同的默认参数将其添加到using
声明符中:
template<int f, class T = Class1B>
using MyClass_t = MyClass<f & optA, f & optB, f & optC, T>;
int main()
{
MyClass_t<optA | optB, std::string> my_obj;
MyClass_t<optB> my_obj_default_T;
// ...
}
执行此操作,因为您尝试在运行时创建需要在编译时定义的某些内容的实例。这样想,create()
的返回类型是什么?它需要MyClass<>
您指定模板参数的位置,但您正在尝试在运行时执行此操作,并且实际上需要在编译时设置它们。
另一种选择是将选项参数提供给构造函数,而不是模板参数,这样您就可以在构造时将它们传入。
class MyClass
{
public:
MyClass(bool optionA, bool optionB, bool optionC);
};
class Builder
{
private:
bool m_OptionA;
bool m_OptionB;
bool m_OptionC;
public:
Builder()
{
m_OptionA = false;
m_OptionB = false;
m_OptionC = false;
}
Builder &useOptionA()
{
m_OptionA = true;
return *this;
}
Builder &useOptionB()
{
m_OptionB = true;
return *this;
}
Builder &useOptionC()
{
m_OptionC = true;
return *this;
}
MyClass create() const
{
return MyClass(m_OptionA, m_OptionB, m_OptionC);
}
};
现在你可以说:
MyClass instance = Builder().useOptionA().useOptionB().create();
考虑一个类似 mpl 的字典。
template<class T>struct tag_t{using type=T; constexpr tag_t(){};};
template<class T>constexpr tag_t<T> tag{};
template<class Tag>using type=typename Tag::type;
template<class K, class V, class...Base>
struct map_entry:Base... {
friend constexpr tag_t<V> get( tag_t<K>, map_entry ){ return {}; }
};
struct no_base{};
template<class Base, class...entries>
struct type_map;
template<class Base>
struct type_map<Base>:Base{};
template<class Base, class K0, class V0, class...B0s, class...Es>
struct type_map<Base, map_entry<K0,V0,B0s...>, Es...>:
map_entry<K0,V0, type_map<Base, B0s..., Es...>>
{};
template<class Map, class Key>
using lookup=type<decltype(get(tag<Key>,std::declval<Map>())>;
要传递像 bool 这样的整数常量,请使用:
template<bool b>using bool_k=std::integral_constant<bool, b>;
integral_constant
·
我们定义标签:
namespace MyTags{
struct OptionA{};
struct OptionB{};
struct OptionC{};
struct T{};
}
填充默认值:
using MyClassDefaults=type_map<
no_base,
map_entry< MyTags::OptionA, bool_k<false> >,
map_entry< MyTags::OptionB, bool_k<false> >,
map_entry< MyTags::OptionC, bool_k<false> >,
map_entry< MyTags::T, Class1B >
>;
template<class Options=MyClassDefaults>
class MyClass {
};
要读取值,请执行lookup<Options,MyTags::OptionA>
。 在类中创建 using
别名以消除重复Options
的需要。
要覆盖,只需:
using Override=type_map<
MyClassDefaults,
map_entry< MyTags::OptionC, bool_k<true> >
>;
代码未测试。
这是实现这一点的正确方法。
假设我们有以下类模板:
template<class TYPE_1, class TYPE_2, int value_1>
struct My_Class {
// ...
};
我们希望能够以这种方式构建它:
auto object = My_Class_Builder::Value_1<42>::Type_2<float>::My_Class();
生成器实现:
// specify default template arguments below:
template<class TYPE_1 = void, class TYPE_2 = void, int value_1 = 0>
struct _My_Class_Builder {
// equivalent of build() function. alternatively you can name it `Build`
using My_Class = ::My_Class<TYPE_1, TYPE_2, value_1>;
template<class NEW_TYPE_1>
using Type_1 = _My_Class_Builder<NEW_TYPE_1, TYPE_2, value_1>;
template<class NEW_TYPE_2>
using Type_2 = _My_Class_Builder<TYPE_1, NEW_TYPE_2, value_1>;
template<int new_value_1>
using Value_1 = _My_Class_Builder<TYPE_1, TYPE_2, new_value_1>;
};
// to make it possible to use without empty `<>`
using My_Class_Builder = _My_Class_Builder<>;
如果您My_Class
了一些必需的模板参数,只需删除最后一个using My_Class_Builder = _My_Class_Builder<>;
声明,并从主生成器类名中删除_
。然后用法变为:
auto object = My_Class_Builder<double>::Value_1<42>::Type_2<float>::My_Class();
没有运行时开销,因为不会创建生成器对象。
您可以在我的三角形网格操作库中看到此模式的实际效果: Smesh_Builder
只需使用枚举类而不是布尔值。
enum class OptionA {TRUE, FALSE}
template <OptionA a> class Foo {};
Foo<OptionA::TRUE> f{};
此外,模板的构建器模式本身只能通过编写许多中间类型来实现。在此过程中,每个生成方法都必须返回一个类型,并且它不能是最终类型。因此,对于每个函数调用,您必须编写一个新的类模板,并将下一个函数调用作为该模板类上的模板函数。因此,在顺序(与普通构建器不同(和大量样板文件方面会很严格。
例:
template <bool OptA, bool OptB> class Bar {};
template <bool OptA>
struct OptASet {
template <bool OptB>
Bar<OptA, OptB> setOptB() { return Bar<OptA, OptB>{}; }
}
struct BarBuilder {
template <bool OptA>
setOptA() { return OptASet<OptA>{} }
}
auto b = BarBuilder.setOptA<true>().setOptB<false>();
正如我所说,它对顺序很严格,你不能省略东西,所以它比构建器模式有用得多,尤其是当你有很多默认参数时。
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 为什么在保护模式下继承升级不起作用
- 如何在全屏模式下(在OpenGL中)使背景透明
- 为什么使用__LINE_的代码在发布模式下在MSVC下编译,而不是在调试模式下
- 派生类是否可以在抽象工厂设计模式中具有数据成员
- 此模式的C++RegEx
- avrogencpp能为模式中的每种类型生成单独的头文件吗
- 使用可变模板的Broadcaster/Listener模式
- c++方法参数只能在linux的发布模式下自行更改
- 资源管理设计模式
- 使用 mod_gsoap 部署服务时,如何在 Gsoap 中更改 soap 上下文的模式?
- C++ 无法在字符数组中使用 for 循环打印字母模式
- 小字符串优化(调试与发布模式)
- 可视化C++:发布模式的运行时库作为'Multi-threaded Debug DLL'
- 如何设计具有不同类型的通知和观察器的观察者模式?
- 在C++的一系列数字中查找重复模式
- 是否允许使用带有"w+"模式的 freopen 进行标准设置?
- C++ 使用存储在动态数组中的文本文件中的数据查找模式
- С++ wxWidgets:代码架构,设计原则和模式
- 以只读模式打开数据库时SQLITE_CANTOPEN错误