C++ 从 void* 中恢复指向未知关联容器的元素
Posted
技术标签:
【中文标题】C++ 从 void* 中恢复指向未知关联容器的元素【英文标题】:C++ Recover from void* the element pointed into unknown associative container 【发布时间】:2018-09-05 08:58:49 【问题描述】:我创建了一个结构,可以接收来自不同关联容器(set、map、multiset 等)的迭代器,并保留指向该元素的指针。
set<ObjX> SetX ...
set<ObjY> SetY ...
map<ObjZ, Data> MapZ ...
struct INPosition
INPosition(set<ObjX>::const_iterator pTMP) : POINTER(&*pTMP)
INPosition(set<ObjY>::const_iterator pTMP) : POINTER(&*pTMP)
INPosition(map<ObjZ, Data>::const_iterator pTMP) : POINTER(&*pTMP)
void show() cout << POINTER << '\t' << typeid(POINTER).name() << '\n';
private:
const void* POINTER;
;
INPosition TTT(SetY.find(ObjY(2)));
cout << "Addr SetY Element 1: " << &*SetY.find(1) << '\t'<< typeid(&*SetY.find(1)).name() << '\n';
cout << "Addr SetY Element 2: " << &*SetY.find(2) << '\t'<< typeid(&*SetY.find(2)).name() << '\n';
cout << "Addr POINTER (Elm 2): "; TTT.show();
结果是:
Addr SetY Element 1: 0x5654ee2096d0 PKSt10ObjYI10MyClassSt14default_deleteIS0_EE
Addr SetY Element 2: 0x5654ee209720 PKSt10ObjYI10MyClassSt14default_deleteIS0_EE
Addr POINTER (Elm 2): 0x5654ee209720 PKv //The typeid.name supposes to be the same than Element 2 above???
POINTER 保留元素地址,但 typeid 无法识别我应该识别的这个元素(与元素 2 相同)。
我的问题是如何创建一个返回具有正确类型的转换的函数成员?换句话说,恢复迭代器。 像这样的:
auto Iterator()
return auto_cast(POINTER);
【问题讨论】:
与您的问题和问题无关,但以下划线开头后跟大写字母的符号(例如_INPosition
)在编译器和标准库的所有范围内都保留。不要使用它们。欲了解更多信息,请参阅What are the rules about using an underscore in a C++ identifier?
至于你的问题,void*
没有任何关于它所指向的信息。作为程序员,你必须知道它指向什么,否则你不能将指针用于任何事情。请注意,使用&*pTMP
,您将获得指向迭代器所引用内容的指针,而不是迭代器本身。迭代器在您的代码中丢失了(但取决于容器,存储迭代器根本没有用处)。
这有an XY problem 的味道。 为什么你需要这个指针?它应该解决的原始问题是什么?
我需要从我不知道元素数量及其顺序的多个来源(不同容器)创建不同元素的向量。保持入场顺序很重要。因为我创建了 struct INPosition。使用最少的空间也很关键,因为我提出的想法是只使用一个 void*
auto_cast
让我想起了DWIM-instruction
。
【参考方案1】:
您不需要void*
,您需要std::variant<ObjX*, ObjY*, ObjZ*>
。
您的show()
可以使用std::variant<...>::index()
,X、Y 或 Z 分别为 0、1 或 2。
【讨论】:
我测试了 std::any,但由于开销,variant 是最好的选择,并且最容易使用 std::variant。【参考方案2】:你不能拥有auto_cast
这样的东西。 C++ 不会在任何地方存储转换为void*
的对象类型。您必须自己知道该类型才能从 void*
中恢复对象,因为它实际上只是指向某个内存位置的指针,没有信息什么可以在那里找到。
您必须在void*
旁边存储有关类型的一些信息。例如,使用枚举:
struct INPosition
enum class ObjType SET_X, SET_Y, MAP_Z ;
INPosition(std::set<ObjX>::const_iterator pTMP) : element(&*pTMP), objType(ObjType::SET_X)
INPosition(std::set<ObjY>::const_iterator pTMP) : element(&*pTMP), objType(ObjType::SET_Y)
INPosition(std::map<ObjZ, Data>::const_iterator pTMP) : element(&*pTMP), objType(ObjType::MAP_Z)
private:
const void* element;
const ObjType objType;
;
这本身不足以让你到达你想要的地方,因为你不能从同一个函数返回不同的类型。你希望auto Iterator()
有什么返回类型?
这里的教训是:通过使用void*
,您明确地击败(或离开)C++ 类型系统,但您不能在它之外编写任何有用的C++ 代码。从void*
回到类型系统需要一些努力。
您可以通过使用(重载的)函子参数来实现一些有用的东西,从而实现这项工作:
template<class OverloadedFunctor>
auto apply(OverloadedFunctor f)
switch (objType)
case ObjType::SET_X:
return f(*static_cast<const std::set<ObjX>::value_type*>(element));
case ObjType::SET_Y:
return f(*static_cast<const std::set<ObjY>::value_type*>(element));
case ObjType::MAP_Z:
return f(*static_cast<const std::map<ObjZ, Data>::value_type*>(element));
如果你有一个重载的仿函数可以对这些类型做一些有用的事情,你可以像这样应用它:
struct MyOverloadedFunctor
void operator()(const ObjX& x) const;
void operator()(const ObjY& y) const;
void operator()(const std::pair<ObjZ, Data>& zd) const;
;
std::set<ObjY> setY;
setY.emplace();
INPosition position(setY.begin());
MyOverloadedFunctor myOverloadedFunctorInstance;
position.apply(myOverloadedFunctorInstance); // will call the second overload.
Demo
【讨论】:
以上是关于C++ 从 void* 中恢复指向未知关联容器的元素的主要内容,如果未能解决你的问题,请参考以下文章