C++ 11 模板变量设计

C++ 11 Template Variable Design

本文关键字:变量 C++      更新时间:2023-10-16

我有很多抽象类字母的子类,如A,B,C,D等。 Letter 有一个整数 ID 变量,Letter 的每个子类都被分配一个唯一的 ID。

然后我还有另一个类,叫它字母表。 字母表有一个

list<shared_ptr<Letter>> 

成员。 这是问题所在...我想优雅地将字母的B和C或其他子类添加到字母的特定实例中。 我认为最方便的方法是以某种方式使用子类的整数 id。 换句话说,我希望能够拥有类似 Alphabet.addLetter(int id( 的东西,所以如果我做 alphabet1.add(14(,它会以某种方式将类 H 的shared_ptr添加到列表中。

有没有一种优雅的方法可以做到这一点,避免一些巨大的 if 语句,每次添加或删除 B、C、D、E 等类之一时,我都需要不断更新? 我希望有某种模板解决方案,但我不太熟悉工厂和模板等高级 c++ 概念。 我想要的天真的东西是某种向量/映射,将我的 id 转换为类名,这样我就可以做类似的事情

list.push_back(shared_ptr<classVector(i)>(new classVector(i))

或类似的东西,虽然我不知道这是否可能。

谢谢!

附言我只是选择了Alphabet的例子,因为我不想给出不必要的细节。 显然,我并没有试图以如此愚蠢的方式设计字母表,哈哈。

编辑:我正在努力使这有意义。 我的目标是能够以最小的努力非常快速地创建 Letter 的新子类。 我想避免输入看起来像...

list.push_back(shared_ptr<X>(...));

每次我写一封新信。 这有意义吗?

这很难理解,但我认为你想要的是以下内容:

// where make_unique<> is from C++14 in std:: or like:
template <typename T, typename ... TArgs>
std::unique_ptr<T> make_unique(TArgs &&... args) {
  return std::unique_ptr<T>(new T(std::forward<TArgs>(args)...));
}
struct Letter {
  virtual ~Letter() { }
  virtual void foo() = 0;
};
template <unsigned int N> struct LetterCode; // Note: no default implementation!
struct Alphabet {
  // Indexed access, if you'll have 1 of each type max:
  std::vector<std::unique_ptr<Letter>> v;
  // If you don't need parameters, as mentioned in comments below ...
  template <unsigned int N>
  void addLetterN() {
    if (N > v.size() + 1) { v.resize(N + 1); }
    v[N] = make_unique<LetterCode<N>::type>(); // see below ...
  }
  // If your coding is complete from 0...N, this does the whole shebang.
  template <unsigned int N>
  void addLettersN() {
    addLetters<N - 1>();
    addLetterN<N>();
  }
  template <>
  addLettersN<0>() {
    addLetterN<0>();
  }
};

如果您需要数字代码来实现反序列化之类的操作,并且永远不需要构造函数参数,则可以使用如下所示的类型特征模板来静态"注册"类型:

struct B : Letter {
  B(int n, bool b, char const *name);
  void foo() override;
};
template <> struct LetterCode<2> { using type = B; };
struct C : Letter {
  C(double d);
  void foo() override;
};
template <> struct LetterCode<3> { using type = C; };
void bar() {
  Alphabet a;
  a.addLetterN<2>();
  a.addLetterN<3>();
  // --OR--
  a.addLettersN<3>(); // will do 0...3 in one fell swoop.
  for (auto &i : a.v) {
    if (!i) { continue; } // v is sparse, unlike l
    i->foo();
}

如果你需要广义构造函数参数传递,你可以使用完美转发,它是为这种情况设计的,并且避免了对旧样式工厂的枚举 ID 等的需求:

struct Alphabet {
  std::list<std::unique_ptr<Letter>> l;
  // variadic factory that chucks new (shared_ptr) objects in the list.
  template <typename T, typename ... TArgs>
  void addLetter(TArgs && ... args) {
    l.push_back(make_unique<T>(std::forward<TArgs>(args)...));
  }
};
void baz() {
  Alphabet a;
  a.addLetter<B>(1, false, "pony");
  a.addLetter<C>(2.718281828);
  for (auto &i : a.l) {
    i->foo(); // can call virtual funcs here all you want ...
  }
}

如果我正确理解你,使用所谓的工厂模式,这是相对容易的。

如果可以列出所有派生类型:

信头:

struct Letter {
    enum LetterEnum {LetterA, LetterB, LetterC, LetterCount};
    
    virtual ~Letter() {} //base types should always have virtual destructor
    virtual void foo() = 0;
  
    static std::unique_ptr<Letter> construct(LetterEnum c);
};

实现标头:

struct A : Letter {
  void foo() override;
};
struct B : Letter {
  void foo() override;
};
struct C : Letter {
  void foo() override;
};

字母正文:

std::unique_ptr<Letter> Letter::construct(Letter::LetterEnum c) 
{
    switch(c) {
    case Letter::LetterA : return make_unique<A>();
    case Letter::LetterB : return make_unique<B>();
    case Letter::LetterC : return make_unique<C>();
    default: throw ...;
    }
}

用法:

int main() {
    char c;
    std::cin >> c;
    //get a letter of the derived type associated with the letter entered
    std::unique_ptr<Letter> ptr = Letter::construct(c);    
}

如果无法列出所有派生类型:

允许派生类型向 Letter 类注册自身,然后Letter可以使用它来创建每个派生类型。 这样,添加和删除派生类型不涉及对任何其他文件的更改。 容易!

struct Letter {
    virtual ~Letter() {} //destructor is always virtual when inheretence is involved
    ....
    //this is a "shared" function in the Letter class itself
    //it takes a letter, and returns a dynamically allocated instance
    //of the derived type corresponding with that letter
    static std::unique_ptr<Letter> construct(char c);
    //this typedef represents the actual function that returns 
    //each dynamically allocated derived type
    typedef std::function<std::unique_ptr<Letter>()> letter_ctor;
    //this is a "shared" function in the Letter class itself
    //it takes a letter, and a function that creates derived types, 
    //and saves them inside the container ctors
    static bool register(char c, letter_ctor func);
private:
    //this is a "shared" member in the Letter class.  
    //There is only one shared by all of the Letters.  Like a global.
    //When you give it a letter, it gives you a function.
    //and is VERY fast for large numbers of entries
    static std::unordered_set<char,letter_ctor> ctors;
};

并在您的实现文件中:

//here's the function that derived types register themselves with
//pretty straightforward, just inserts the pair into the unordered_map
bool Letter::register(char c, Letter::letter_ctor func)
{return Letter::ctors.insert(std::make_pair(c,std::move(func))).second;}
//and here's the function that creates the derived types
//it checks if the letter is in the unordered_map
//if the letter isn't there, it throws an exception
//otherwise, it calls the function associated with that letter
//which creates the derived type on the heap, and returns a pointer to it
std::unique_ptr<Letter> Letter::construct(char c) 
{
     auto it = Letter::ctors.find(c);
     if (it == Letter::ctors.end())
         throw ...;
     return it->second(); //construct that letter
}

然后派生类型执行以下操作:

//you know this part
struct LetterA : public Letter 
{
   ....
};
//derived types have to register themselves:
//this is a global, so when the program loads, it automatically calls this
//even before main runs*
//it registers the letter 'A' and a function that creates a LetterA class on the heap
static bool registerA = Letter::register('A', [](){return make_unique<LetterA>();});

然后,您可以轻松创建树木派生类型!

int main() {
    char c;
    std::cin >> c;
    //get a letter of the derived type associated with the letter entered
    std::unique_ptr<Letter> ptr = Letter::construct(c);
}

*它并不总是在 main 之前被调用。 如果遇到问题,请在 A 标头中放置一个bool init_A();,在 A 实现文件中放置bool init_A(){return true;},并在主文件中static bool AInit=init_A();强制使用。 不过,这在实践中几乎从来都不需要。


作为旁注,这些取决于有一个 make_unique它应该在 C++11 中,但由于疏忽而被遗漏。 它将在C++14。 同时,请使用以下内容:

template<class T, class...Us>
std::unique_ptr<T> make_unique(Us&&...us) 
{return std::unique_ptr<T>(new T(std::forward<Us>(us)...));}

我的理解是,您希望创建其中一个类的实例,这取决于与应从中创建实例的类相关的 id。

如果是这样,请查看工厂模式。有很多工厂实现,也是基于类型列表的模板递归扩展。

伪代码:

Factory<A,B,C,D> fac; // the list must be changed, if some more classes comes and goes
id_type id;
list<base> l;
l.push_back=fac.Create(id);

自己实现这样的类也很简单。

目标很简单:创建返回 Alphabet 生成器数组的函数工厂。

字母的索引和数组中的索引将是相同的。

理想情况下,我们希望自动生成所述索引,而无需手动设置它。

#include <memory>
#include <vector>
#include <iostream>
template<class T>using Type=T;
template<class...Ts>struct types:std::integral_constant<unsigned,sizeof...(Ts)>
{typedef types type;};
template<class T,class types>struct index_of;
template<class T,class T0, class...Ts>struct index_of<T,types<T0,Ts...>>:
  std::integral_constant<unsigned,index_of<T,types<Ts...>>::value+1>
{};
template<class T,class...Ts>struct index_of<T,types<T,Ts...>>:
  std::integral_constant<unsigned,0>
{};
template<unsigned,class types>struct type_at;
template<unsigned N, class T,class...Ts>struct type_at<N,types<T,Ts...>>:
  type_at<N-1,types<Ts...>> {};
template<class T,class...Ts>struct type_at<0,types<T,Ts...>>{
  typedef T type;
};
template<unsigned N,class types>
using type_at_t=typename type_at<N,types>::type;
template<template<class>class Target,unsigned N,class types>
struct nth_apply;
template<template<class>class Target,unsigned N,class...Ts>
struct nth_apply<Target,N,types<Ts...>>{
  typedef Target<type_at_t<N,types<Ts...>>> type;
};
template<template<class>class Target,unsigned N,class types>
using nth_apply_t=typename nth_apply<Target,N,types>::type;

这是为我们生成函数指针的类型:

template<class T>struct shared_maker{
  template<class...Args>
  std::shared_ptr<T> operator()(Args&&...args)const{
    return std::make_shared<T>(std::forward<Args>(args)...);
  }
  template<class R, class... Args>
  operator Type<R(Args...)>*() const{
    return [](Args... args)->R{
      return shared_maker{}(std::forward<Args>(args)...);
    };
  }
};

以下是我们对实际字母类型所做的工作。 我们转发声明它们:

struct A; struct B; // etc

将它们粘贴到类型列表中:

typedef types<A,B> Alphabet_Types;

现在,我们的简单测试Alphabet类型:

struct Alphabet {
  virtual unsigned get_index() const = 0;
};

还有一个 CRTP 帮助程序,它将字母的索引从其偏移量获取到类型列表中! virtual get_indexes仅用于调试:

template<class D>
struct Letter:Alphabet{
  static const unsigned index = index_of<D, Alphabet_Types>::value;
  virtual unsigned get_index() const override { return index; }
};

现在是我们的阵列生产者的签名:

typedef std::shared_ptr<Alphabet> spAlphabet;
std::array<spAlphabet(*)(), Alphabet_Types::value> factories();

以下是我们如何定义我们的(玩具(字母类:

struct A:Letter<A>{};
struct B:Letter<B>{};

即,使用字母<>作为CRTP基础而不是字母表。

剩下的唯一事情就是将函数编写factories

索引样板。 C++1y 有一个替代品:

template<unsigned...>struct indexes{typedef indexes type;};
template<unsigned Max, unsigned... Is> struct make_indexes:make_indexes<Max-1,Max-1,Is...>{};
template<unsigned...Is>struct make_indexes<0,Is...>:indexes<Is...>{};

通过帮助程序函数实现的实际实现。 我们得到一包索引并对其进行扩展,从上面的shared_maker构建我们的函数指针std::array,并使用我们上面写Alphabet_Types的索引类型实例化:

template<unsigned...Is>
std::array<spAlphabet(*)(), Alphabet_Types::value> factories(indexes<Is...>){
  return {nth_apply_t<shared_maker,Is,Alphabet_Types>{}...};
}

实际的factories函数只是转发给上面的助手:

std::array<spAlphabet(*)(), Alphabet_Types::value> factories(){
  return factories(make_indexes<Alphabet_Types::value>{});
}

还有一些琐碎的测试代码:

int main() {
  std::vector<spAlphabet> vec;
  auto builders = factories();
  for (int i = 0; i < 2; ++i) {
    vec.push_back(builders[i]());
  }
  for( auto&& ptr:vec ) {
    std::cout << ptr->get_index() << "n";
  }
}