C ++ 11模板变量设计

时间:2014-05-15 20:45:44

标签: c++ c++11

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

然后我有另一个班,称之为Alphabet。字母表有一个

list<shared_ptr<Letter>> 

构件。这是问题...我想优雅地将B和C或其他子类的Letter添加到字母的特定实例中。我认为最方便的方法是以某种方式使用子类的整数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))

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

谢谢!

P.S。我刚刚选择了Alphabet示例,因为我不想给出不必要的细节。显然,我不是试图以这种愚蠢的方式设计字母表,哈哈。

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

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

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

4 个答案:

答案 0 :(得分:8)

这很难遵循,但我认为您想要的是以下内容:

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

如果您需要反序列化之类的数字代码并且永远不需要构造函数参数,则可以使用类似以下的类型特征模板来静态地注册&#39;类型:

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 ...
  }
}

答案 1 :(得分:4)

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

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

信头:

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可以使用它来创建每个派生类型。这样,添加和删除派生类型涉及 no 对任何其他文件的更改。简单!

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

然后您可以轻松创建arbirary派生类型!

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

*它不会在主要之前总是被调用。如果您遇到问题,请在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)...));}

答案 2 :(得分:1)

我的理解是你要创建一个类的实例,依赖于一个id,该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);

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

答案 3 :(得分:0)

目标很简单:创建返回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>{};

即,使用Letter&lt;&gt;作为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...>{};

通过辅助函数实际实现。我们获得了一组索引并对其进行了扩展,从上面的std::array构建了shared_maker函数指针,并使用我们上面编写的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";
  }
}