基本上我希望MyClass包含一个将字段名称(字符串)映射到任何类型的Hashmap 值..为此,我写了一个单独的MyField类,它保存了类型&价值信息..
这是我到目前为止所做的:
template <typename T>
class MyField {
T m_Value;
int m_Size;
}
struct MyClass {
std::map<string, MyField> fields; //ERROR!!!
}
但正如您所看到的,地图声明失败,因为我没有为MyField提供类型参数...
所以我猜它必须像
std::map< string, MyField<int> > fields;
或
std::map< string, MyField<double> > fields;
但显然这破坏了我的整个目的,因为声明的地图只能保存特定类型的MyField。我想要一个可以容纳任何类型的MyField clas的地图..
有什么办法可以实现这个目的吗?
答案 0 :(得分:24)
Blindy的回答非常好(+1),但只是为了完成答案:通过使用动态继承,还有另一种方法可以做到这一点:
class MyFieldInterface
{
int m_Size; // of course use appropriate access level in the real code...
~MyFieldInterface() = default;
}
template <typename T>
class MyField : public MyFieldInterface {
T m_Value;
}
struct MyClass {
std::map<string, MyFieldInterface* > fields;
}
优点:
缺点:
因此,如果可以,请使用boost :: any或boost :: variant作为默认值,并且仅考虑此选项。
要修复最后一点,你可以使用智能指针:
struct MyClass {
std::map<string, std::unique_ptr<MyFieldInterface> > fields; // or shared_ptr<> if you are sharing ownership
}
然而,仍然存在一个可能更有问题的一点:
它强制您使用new / delete(或make_unique / shared)创建对象。这意味着实际对象是在分配器提供的任何位置(主要是默认值)的免费存储(堆)中创建的。因此,由于cache misses,经常访问对象列表的速度并不快。
如果您关注通过此列表循环的性能,请尽可能快地(如果没有,请忽略以下内容),那么您最好使用boost :: variant(如果已经知道你将使用的所有具体类型)或使用某种类型擦除的多态容器。
这个想法是容器将管理相同类型的对象数组,但仍然暴露相同的接口。该接口可以是一个概念(使用鸭子类型技术)或动态接口(像我的第一个例子中的基类)。 优点是容器将相同类型的对象保存在单独的向量中,因此通过它们很快。只从一种类型转到另一种类型不是。
以下是一个示例(图片来自那里):http://bannalia.blogspot.fr/2014/05/fast-polymorphic-collections.html
但是,如果你需要保持插入对象的顺序,这种技术就会失去兴趣。
无论如何,有几种可能的解决方案,这在很大程度上取决于您的需求。如果你对你的案例没有足够的经验,我建议使用我在我的例子中首先解释的简单解决方案或者boost :: any / variant。
作为这个答案的补充,我想指出非常好的博客文章,总结了你可以使用的所有C ++类型擦除技术,评论和优点/缺点:
答案 1 :(得分:16)
使用boost::variant
(如果您知道可以存储的类型,它提供编译时支持)或boost::any
(对于任何类型 - 但实际情况不太可能)。
http://www.boost.org/doc/libs/1_55_0/doc/html/variant/misc.html#variant.versus-any
编辑:我不能强调,虽然滚动你自己的解决方案看起来很酷,但是从长远来看,使用完整,正确的实现将为你节省很多麻烦。 boost::any
实现了RHS复制构造函数(C ++ 11),包括安全(typeid()
)和不安全(哑元组)值检索,const
核心,RHS操作数以及指针和值类型。
一般情况下这是正确的,但对于低级别的基本类型来说更是如此。您可以构建整个应用程序。
答案 2 :(得分:8)
class AnyBase
{
public:
virtual ~AnyBase() = 0;
};
inline AnyBase::~AnyBase() {}
template<class T>
class Any : public AnyBase
{
public:
typedef T Type;
explicit Any(const Type& data) : data(data) {}
Any() {}
Type data;
};
std::map<std::string, std::unique_ptr<AnyBase>> anymap;
anymap["number"].reset(new Any<int>(5));
anymap["text"].reset(new Any<std::string>("5"));
// throws std::bad_cast if not really Any<int>
int value = dynamic_cast<Any<int>&>(*anymap["number"]).data;
答案 3 :(得分:6)
这在C ++ 17中很简单。使用std :: map + std :: any + std :: any_cast:
#include <map>
#include <string>
#include <any>
main()
{
std::map<std::string, std::any> Notebook;
std::string name{ "Pluto" };
int year = 2015;
Notebook["PetName"] = name;
Notebook["Born"] = year;
std::string strS = std::any_cast<std::string>(Notebook["PetName"]);; // = "Pluto"
int intI = std::any_cast<int>(Notebook["Born"]); // = 2015
}
答案 4 :(得分:3)
C ++ 17具有std::variant
类型,它具有比联合更好地保存不同类型的工具。
对于那些不在C ++ 17上的人,boost::variant
实现了同样的机制。
对于那些没有使用boost的人来说,https://github.com/mapbox/variant为C ++ 11和C ++ 14实现了更轻的variant
版本,看起来非常有前途,文档齐全,轻量级,并且有很多用法实例
答案 5 :(得分:0)
您还可以使用void *并使用reinterpret_cast将值强制转换回正确的类型。它是一种常用于回调中的C语言。
#include <iostream>
#include <unordered_map>
#include <string>
#include <cstdint> // Needed for intptr_t
using namespace std;
enum TypeID {
TYPE_INT,
TYPE_CHAR_PTR,
TYPE_MYFIELD
};
struct MyField {
int typeId;
void * data;
};
int main() {
std::unordered_map<std::string, MyField> map;
MyField anInt = {TYPE_INT, reinterpret_cast<void*>(42) };
char cstr[] = "Jolly good";
MyField aCString = { TYPE_CHAR_PTR, cstr };
MyField aStruct = { TYPE_MYFIELD, &anInt };
map.emplace( "Int", anInt );
map.emplace( "C String", aCString );
map.emplace( "MyField" , aStruct );
int intval = static_cast<int>(reinterpret_cast<intptr_t>(map["Int"].data));
const char *cstr2 = reinterpret_cast<const char *>( map["C String"].data );
MyField* myStruct = reinterpret_cast<MyField*>( map["MyField"].data );
cout << intval << '\n'
<< cstr << '\n'
<< myStruct->typeId << ": " << static_cast<int>(reinterpret_cast<intptr_t>(myStruct->data)) << endl;
}