C++保留了一组指向模板对象的指针,这些指针都派生自一个非模板类
C++ keeping a collection of pointers to template objects, all derived from a non-template class
我有一个对象"标识符"列表(一个长枚举列表,每个"标识符"有一个唯一值):
enum Identifier {
Enum0, // an identifier for a bool value
Enum1, // ... for a float value
Enum2, // ... for an int value
// etc.
};
我希望维护与这些标识符相关联的Value对象的集合。这些Value对象包含一个值,但该值可以是整数、浮点、布尔值或其他(简单)类型。这是在管理系统中的一组配置值的上下文中进行的。稍后,我计划扩展这些值类型以支持内部值的验证,并将一些值与其他值关联起来。
然而,我希望为这些Value类使用模板,因为我希望对这些Value进行一般性的操作。如果我使用继承,我会有BaseValue,然后从BaseValue派生IntValue、FloatValue等。相反,我有Value、Value等
但我也想在一个集合中存储对每个值的访问机制。我想要一个类来实例化所有它们,并在集合中维护它们。如果我使用继承,我可以使用指向BaseValue的指针向量。但因为我使用的是模板,所以这些类之间并没有多态性的关联。
所以我想让它们基于一个(空的?)抽象基类,这个基类是而不是参数化的:
class BaseParameter {
};
template<typename T>
class Parameter : public BaseParameter {
public:
explicit Parameter(T val) : val_(val) {}
void set(ParameterSource src) { val_ = extract<T>(src); }
T get() { return val_; };
private:
T val_;
};
请注意,"set"成员函数采用"ParameterSource",它是由特定的"to_type"函数"重新解释"的值的来源。这是一个我无法控制的API函数——我必须自己解释类型,因为我知道类型的含义,如下所示。这就是extract的作用——它专门用于各种T类型,如float、int、bool。
然后我可以将它们添加到std::向量中,如下所示:
std::vector<BaseParameter *> vec(10);
vec[Enum0] = new Parameter<bool>(true); // this is where I state that it's a 'bool'
vec[Enum1] = new Parameter<float>(0.5); // ... or a float ...
vec[Enum2] = new Parameter<int>(42); // ... or an int ...
我知道我可能应该使用unique_ptr,但现在我只是想让它发挥作用。到目前为止,这似乎运行良好。但我对此持谨慎态度,因为我不确定在运行时是否会保留完整类型的实例化模板。
稍后,我想通过任意枚举值对"vec"进行索引,检索参数并调用其上的成员函数:
void set_via_source(Identifier id, ParameterSource source) {
// if id is in range...
vec[id]->set(source);
}
其他使用这些配置值(因此知道类型)的代码可以使用访问它们
int foo = vec[Enum2]->get() * 7;
大多数情况下,这似乎奏效了。它编译。我遇到过一些奇怪的崩溃,我无法解释,这些崩溃也会导致调试器崩溃。但我对此非常怀疑,因为我不知道C++是否能够确定指向对象的真实类型(包括参数化类型),因为基类本身并没有参数化。
不幸的是,在我看来,如果我对基类进行参数化,那么我基本上消除了这些Value类之间的共性,这些共性允许它们存储在一个容器中。
我看了一眼boost::any,看看这是否有帮助,但我不确定它是否适用于这种情况。
在更高的层次上,我试图连接来自外部源的大量配置项集合(通过API),该外部源根据项提供不同类型的值,并将它们存储在本地,以便我的代码的其余部分可以轻松地访问它们,就好像它们是简单的数据成员一样。我还想避免写一个巨大的switch语句(因为这会起作用)。
这是打字橡皮擦可以帮助我的东西吗?
如果您在编译时知道与每个枚举关联的类型,则可以使用boost::variant
"轻松"执行此操作,而无需类型擦除甚至继承。(编辑:第一个解决方案使用了大量C++11功能。我在最后提出了一个自动化程度较低但符合C++03的解决方案。)
#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/get.hpp>
// Here's how you define your enums, and what they represent:
enum class ParameterId {
is_elephant = 0,
caloric_intake,
legs,
name,
// ...
count_
};
template<ParameterId> struct ConfigTraits;
// Definition of type of each enum
template<> struct ConfigTraits<ParameterId::is_elephant> {
using type = bool;
};
template<> struct ConfigTraits<ParameterId::caloric_intake> {
using type = double;
};
template<> struct ConfigTraits<ParameterId::legs> {
using type = int;
};
template<> struct ConfigTraits<ParameterId::name> {
using type = std::string;
};
// ...
// Here's the stuff that makes it work.
class Parameters {
private:
// Quick and dirty uniquifier, just to show that it's possible
template<typename...T> struct TypeList {
using variant = boost::variant<T...>;
};
template<typename TL, typename T> struct TypeListHas;
template<typename Head, typename...Rest, typename T>
struct TypeListHas<TypeList<Head, Rest...>, T>
: TypeListHas<TypeList<Rest...>, T> {
};
template<typename Head, typename...Rest>
struct TypeListHas<TypeList<Head, Rest...>, Head> {
static const bool value = true;
};
template<typename T> struct TypeListHas<TypeList<>, T> {
static const bool value = false;
};
template<typename TL, typename T, bool B> struct TypeListMaybeAdd;
template<typename TL, typename T> struct TypeListMaybeAdd<TL, T, false> {
using type = TL;
};
template<typename...Ts, typename T>
struct TypeListMaybeAdd<TypeList<Ts...>, T, true> {
using type = TypeList<Ts..., T>;
};
template<typename TL, typename T> struct TypeListAdd
: TypeListMaybeAdd<TL, T, !TypeListHas<TL, T>::value> {
};
template<typename TL, int I> struct CollectTypes
: CollectTypes<typename TypeListAdd<TL,
typename ConfigTraits<ParameterId(I)>::type
>::type, I - 1> {
};
template<typename TL> struct CollectTypes<TL, 0> {
using type = typename TypeListAdd<TL,
typename ConfigTraits<ParameterId(0)>::type
>::type::variant;
};
public:
using value_type =
typename CollectTypes<TypeList<>, int(ParameterId::count_) - 1>::type;
template<ParameterId pid>
using param_type = typename ConfigTraits<pid>::type;
// It would be better to not initialize all the values twice, but this
// was easier.
Parameters() : values_(size_t(ParameterId::count_)) {
clear(std::integral_constant<int, int(ParameterId::count_) - 1>());
}
// getter for when you know the id at compile time. Should have better
// error checking.
template<ParameterId pid>
typename ConfigTraits<pid>::type get() {
// The following will segfault if the value has the wrong type.
return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]);
}
// setter when you know the id at compile time
template<ParameterId pid>
void set(typename ConfigTraits<pid>::type new_val) {
values_[int(pid)] = new_val;
}
// getter for an id known only at runtime; returns a boost::variant;
value_type get(ParameterId pid) {
return values_[int(pid)];
}
private:
// Initialize parameters to default values of the correct type
template<int I> void clear(std::integral_constant<int, I>) {
values_[I] = param_type<ParameterId(I)>();
clear(std::integral_constant<int, I - 1>());
}
void clear(std::integral_constant<int, 0>) {
values_[0] = param_type<ParameterId(0)>();
}
std::vector<value_type> values_;
};
// And finally, a little test
#include <iostream>
int main() {
Parameters parms;
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
parms.set<ParameterId::is_elephant>(true);
parms.set<ParameterId::caloric_intake>(27183.25);
parms.set<ParameterId::legs>(4);
parms.set<ParameterId::name>("jumbo");
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
return 0;
}
对于那些还不能使用C++11的人来说,这里有一个使用非类枚举的版本,它不够智能,无法单独构建boost::variant
类型,因此您必须手动提供它:
#include <string>
#include <vector>
#include <boost/variant.hpp>
#include <boost/variant/get.hpp>
// Here's how you define your enums, and what they represent:
struct ParameterId {
enum Id {
is_elephant = 0,
caloric_intake,
legs,
name,
// ...
count_
};
};
template<int> struct ConfigTraits;
// Definition of type of each enum
template<> struct ConfigTraits<ParameterId::is_elephant> {
typedef bool type;
};
template<> struct ConfigTraits<ParameterId::caloric_intake> {
typedef double type;
};
template<> struct ConfigTraits<ParameterId::legs> {
typedef int type;
};
template<> struct ConfigTraits<ParameterId::name> {
typedef std::string type;
};
// ...
// Here's the stuff that makes it work.
// C++03 doesn't have integral_constant, so we need to roll our own:
template<int I> struct IntegralConstant { static const int value = I; };
template<typename VARIANT>
class Parameters {
public:
typedef VARIANT value_type;
// It would be better to not initialize all the values twice, but this
// was easier.
Parameters() : values_(size_t(ParameterId::count_)) {
clear(IntegralConstant<int(ParameterId::count_) - 1>());
}
// getter for when you know the id at compile time. Should have better
// error checking.
template<ParameterId::Id pid>
typename ConfigTraits<pid>::type get() {
// The following will segfault if the value has the wrong type.
return *boost::get<typename ConfigTraits<pid>::type>(&values_[int(pid)]);
}
// setter when you know the id at compile time
template<ParameterId::Id pid>
void set(typename ConfigTraits<pid>::type new_val) {
values_[int(pid)] = new_val;
}
// getter for an id known only at runtime; returns a boost::variant;
value_type get(ParameterId::Id pid) {
return values_[int(pid)];
}
private:
// Initialize parameters to default values of the correct type
template<int I> void clear(IntegralConstant<I>) {
values_[I] = typename ConfigTraits<I>::type();
clear(IntegralConstant<I - 1>());
}
void clear(IntegralConstant<0>) {
values_[0] = typename ConfigTraits<0>::type();
}
std::vector<value_type> values_;
};
// And finally, a little test
#include <iostream>
int main() {
Parameters<boost::variant<bool, int, double, std::string> > parms;
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
parms.set<ParameterId::is_elephant>(true);
parms.set<ParameterId::caloric_intake>(27183.25);
parms.set<ParameterId::legs>(4);
parms.set<ParameterId::name>("jumbo");
std::cout << ('(' + parms.get<ParameterId::name>() + ')')<< ' '
<< parms.get<ParameterId::is_elephant>() << ' '
<< parms.get<ParameterId::caloric_intake>() << ' '
<< parms.get<ParameterId::legs>() << std::endl;
return 0;
}
- 为什么使用 "this" 指针调用派生成员函数?
- 如何使用基类指针引用派生类成员
- 使用基类指针创建对象时,缺少派生类析构函数
- 有没有一种"cleaner"的方法可以在指向基的指针向量中找到派生类的第一个实例?
- 如果基类包含双指针成员,则派生类的构造函数
- 如何在不使用指针的情况下将派生类的对象作为参数传递给基类中的函数?
- 如何在工厂方法中返回指向基于基础操作系统的派生类的有效指针
- 指向基类派生类的 std::unique_ptr 的指针
- 当我在 C++ 中将派生类的指针分配给指针时,地址会更改
- 从基指针到派生的强制转换问题
- 派生类是从基类继承 v 指针并仅使用它,还是也有自己的 v 指针?
- 从纯虚拟类 (A) 派生的指针无法访问来自纯类 (B) 的重载方法
- 如何模板化堆栈分配的多态指针数组到接口,包括派生类型的相应点?
- 从基类指针派生派生类的模板类型
- 指向从指针派生类成员函数的指针,指向基类成员函数
- 访问通过指针派生的类
- 如果未使用虚拟函数,则使用基类指针派生类的任何目的
- 从基指针C++派生类的类型
- 无法从指针派生类转换为指针基类(多态性)
- C++ - 传递智能指针派生类