C++ C++;包含任何类型值的映射
基本上,我希望MyClass持有一个Hashmap,该Hashmap将字段名(字符串)映射到任何类型的 价值为此,我编写了一个单独的MyField类,它保存类型和值信息 这就是我到目前为止所做的:C++ C++;包含任何类型值的映射,c++,templates,stl,stdmap,stdany,C++,Templates,Stl,Stdmap,Stdany,基本上,我希望MyClass持有一个Hashmap,该Hashmap将字段名(字符串)映射到任何类型的 价值为此,我编写了一个单独的MyField类,它保存类型和值信息 这就是我到目前为止所做的: template <typename T> class MyField { T m_Value; int m_Size; } struct MyClass { std::map<string, MyField> fields; //ERROR!!
template <typename T>
class MyField {
T m_Value;
int m_Size;
}
struct MyClass {
std::map<string, MyField> fields; //ERROR!!!
}
模板
类MyField{
T m_值;
国际货币单位大小;
}
结构MyClass{
std::映射字段;//错误!!!
}
但正如您所看到的,映射声明失败,因为我没有为MyField提供类型参数
所以我想应该是这样的
std::map< string, MyField<int> > fields;
std::map字段;
或
std::map字段;
但这显然破坏了我的全部目的,因为声明的映射只能保存特定类型的MyField。。我想要一张能容纳任何类型MyField类别的地图
有什么方法可以实现这一点吗?使用
boost::variant
(如果您知道可以存储的类型,它提供编译时支持)或boost::any
(对于真正的任何类型,但这不太可能)
编辑:尽管滚动您自己的解决方案看起来很酷,但从长远来看,使用完整、正确的实现将为您节省大量的麻烦,这一点我再强调也不为过<代码>boost::any实现RHS复制构造函数(C++11),包括安全(typeid()
)和不安全(哑类型转换)值检索,以及const
corectness、RHS操作数以及指针和值类型
一般来说,这是正确的,但对于构建整个应用程序所基于的低级基本类型,情况更是如此。classanybase
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;
{
公众:
virtual~AnyBase()=0;
};
内联AnyBase::~AnyBase(){}
模板
类Any:公共AnyBase
{
公众:
T型;
显式任意(常量类型和数据):数据(数据){}
Any(){}
类型数据;
};
std::map anymap;
anymap[“number”]。重置(新的任意(5));
anymap[“文本”]。重置(新的任何(“5”));
//抛出std::如果不是真的抛出,则抛出错误
int value=dynamic_cast(*anymap[“number”])。数据;
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;
}
类MyFieldInterface
{
int m_Size;//当然,在实际代码中使用适当的访问级别。。。
~MyFieldInterface()=默认值;
}
模板
类MyField:公共MyFieldInterface{
T m_值;
}
结构MyClass{
std::映射字段;
}
优点:
-
任何C++编码器都是熟悉的
- 它不会强迫你使用Boost(在某些情况下你是不允许的)李>
- 您必须在堆/空闲存储上分配对象,并使用引用语义而不是值语义来操作它们李>
- 以这种方式暴露的公共继承可能会导致过度使用动态继承,以及与类型相关的许多长期问题实际上过于相互依赖李>
- 指针向量如果必须拥有对象,就有问题,因为你必须管理破坏李>
使用Booo::任何或Booost::如果可以,变量为默认值,仅考虑此选项。
要修复最后一点,可以使用智能指针:struct MyClass {
std::map<string, std::unique_ptr<MyFieldInterface> > fields; // or shared_ptr<> if you are sharing ownership
}
struct MyClass{
std::map fields;//或共享\u ptr(如果共享所有权)
}
然而,还有一个潜在的更大问题:
它强制您使用新建/删除(或使_唯一/共享)创建对象。这意味着实际对象是在分配器提供的任意位置(主要是默认位置)的空闲存储(堆)中创建的。因此,经常浏览对象列表的速度并不如它可能的快,因为
如果您关心尽可能快地循环此列表的性能(如果没有,请忽略以下内容),那么您最好使用boost::variant(如果您已经知道要使用的所有具体类型)或某种类型的擦除多态容器
其思想是容器将管理相同类型的对象数组,但仍然公开相同的接口。该接口可以是一个概念(使用duck类型技术)或一个动态接口(如我的第一个示例中的基类)。
其优点是容器将相同类型的对象保存在单独的向量中,因此快速遍历它们。仅仅从一种类型转换到另一种类型是不可能的
下面是一个示例(图像来自于此):
然而,如果您需要保持对象插入的顺序,那么这种技术就失去了它的兴趣
无论如何,有几种可能的解决方案,这在很大程度上取决于您的需求。如果您对您的案例没有足够的经验,我建议使用我在示例中首先解释的简单解决方案或boost::any/variant
作为这个答案的补充,我想指出非常好的博客文章,它总结了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;
}
#包括
#包括
#包括
#包括//输入所需
使用名称空间std;
枚举类型ID{
类型_INT,
类型_CHAR _PTR,
类型_MYFIELD
};
结构MyField{
int-typeId;
作废*数据;
};
int main(){
std::无序地图;
MyField anInt={TYPE_INT,reinterpret_cast(42)};
char cstr[]=“非常好”;
MyField aCString={TYPE_CHAR_PTR,cstr};
MyField aStruct={TYPE_MyField,&anInt};
地图安放位置(“Int”,anInt);
地图放置(“C字符串”,AC字符串);
地图安放位置(“MyField”,aStruct);
int intval=static_cast(重新解释_cast(map[“int”].data));
const char*cstr2=重新解释强制转换(映射[“C Strin
#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;
}
#include <map>
#include <string>
#include <any>
int main()
{
std::map<std::string, std::any> notebook;
std::string name{ "Pluto" };
int year = 2015;
notebook["PetName"] = name;
notebook["Born"] = year;
std::string name2 = std::any_cast<std::string>(notebook["PetName"]); // = "Pluto"
int year2 = std::any_cast<int>(notebook["Born"]); // = 2015
}