我有很多抽象类字母的子类,如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>(...));
每次我写一封新信。这有意义吗?
答案 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";
}
}