C++ 11 模板变量设计
C++ 11 Template Variable Design
我有很多抽象类字母的子类,如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";
}
}
- 如何创建一个CMake变量,除非显式重写,否则使用默认值
- 将成员变量添加到共享库中的类中,不会破坏二进制兼容性吗
- 将数组的地址分配给变量并删除
- 为"adjacent"变量赋值时出现问题
- enum是C++中的宏变量还是整数变量
- 在全局变量中保存类的实例以重新创建类(创建"backup")
- 用C++中的一个变量定义一个常量
- 具有奇怪重复模板模式的派生类中的成员变量已损坏
- 你能重载对象变量名本身返回的内容吗
- 内置函数可查看CPP中的成员变量
- 是否可以初始化不可复制类型的成员变量(或基类)
- 尝试通过多个向量访问变量时,向量下标超出范围
- 试图让变量检查数组中的某些内容
- Cpp-Tuple使用带有变量的get
- 将包含C样式数组的对象初始化为成员变量(C++)
- 当vector是tje全局变量时,c++中vector的内存管理
- 通过多个头文件使用常量变量
- std::threads可以从Windows DLL中的全局变量创建/销毁吗?
- 执行函数时导致崩溃的变量
- 变量没有改变?通过向量的函数调用