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* 没有任何关于它所指向的信息。作为程序员,你必须知道它指向什么,否则你不能将指针用于任何事情。请注意,使用&amp;*pTMP,您将获得指向迭代器所引用内容的指针,而不是迭代器本身。迭代器在您的代码中丢失了(但取决于容器,存储迭代器根本没有用处)。 这有an XY problem 的味道。 为什么你需要这个指针?它应该解决的原始问题是什么? 我需要从我不知道元素数量及其顺序的多个来源(不同容器)创建不同元素的向量。保持入场顺序很重要。因为我创建了 struct INPosition。使用最少的空间也很关键,因为我提出的想法是只使用一个 void* auto_cast 让我想起了DWIM-instruction 【参考方案1】:

您不需要void*,您需要std::variant&lt;ObjX*, ObjY*, ObjZ*&gt;

您的show() 可以使用std::variant&lt;...&gt;::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* 中恢复指向未知关联容器的元素的主要内容,如果未能解决你的问题,请参考以下文章

C++(笔记)容器(vector)作为函数参数如何传参

C++从入门到入土第二十篇:关联式容器-map和set

C++从入门到入土第二十篇:关联式容器-map和set

C++从入门到入土第二十篇:关联式容器-map和set

从 C++ 的成员函数中获取指向成员函数的指针

怎么将vista系统下文件打开方式恢复到以前的未知文件类型