具有C++模板的生成器模式

Builder Pattern with C++ templates

本文关键字:模式 C++ 具有      更新时间:2023-10-16

我有一个高度可配置的类,其中包含许多模板参数,如下所示:

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

正如我所说,它对顺序很严格,你不能省略东西,所以它比构建器模式有用得多,尤其是当你有很多默认参数时。