【问题标题】:Save reference to void pointer in a vector during loop iteration在循环迭代期间保存对向量中的 void 指针的引用
【发布时间】:2018-11-28 12:48:10
【问题描述】:

伙计们,我有这样的功能(这是给定的,不应修改)。

void readData(int &ID, void*&data, bool &mybool) {
    if(mybool)
    {
       std::string a = "bla";
       std::string* ptrToString = &a;
       data = ptrToString;
    }
    else
    {
        int b = 9;
       int* ptrToint = &b;
       data = ptrToint;
    }
}

所以我想在循环中使用这个函数并将返回的函数参数保存在一个向量中(对于每次迭代)。 为此,我编写了以下结构:

template<typename T>
struct dataStruct {
     int id;
     T** data;   //I first has void** data, but would not be better to 
                 //  have the type? instead of converting myData back 
                 // to void* ?
     bool mybool;
};

我的 main.cpp 然后看起来像这样:

int main()
{
    void* myData = nullptr;
    std::vector<dataStruct> vec;  // this line also doesn't compile. it need the typename
    bool bb = false;

    for(int id = 1 ; id < 5; id++) {
        if (id%2) { bb = true; }
        readData(id, myData, bb);       //after this line myData point to a string
        vec.push_back(id, &myData<?>); //how can I set the template param to be the type  myData point to?       
    }
}

或者没有模板有更好的方法吗?我用的是c++11(我不能用c++14)

【问题讨论】:

  • Guys I have a function like this (this is given and should not be modified). 谁给的?该函数中有很多未定义的行为。
  • 谁给你这个功能需要停止编码。您正在获取指向局部变量的指针,这意味着当函数结束时它们是悬空指针。
  • 返回一个局部变量的地址。把它还给给你的人。
  • 你能引用void指针吗?
  • @codekaizer:可以吗?是的。应该?没有。

标签: c++ pointers pass-by-reference


【解决方案1】:

你说不能修改的函数,即readData()是应该提醒你的!

它会导致Undefined Behavior,因为指针被设置为局部变量,这意味着当函数终止时,这些指针将是悬空指针。

【讨论】:

  • 从技术上讲,该功能本身不会导致 UB。如果您尝试使用它存储在data 中的任何东西,您只会在它返回后获得 UB。但是不,它没有做任何甚至模糊有用的事情——任何尝试使用它产生的东西都会产生 UB。
  • @codekaizer 对事物的引用与事物相同。这只是意味着来自呼叫站点的东西将注册任何更改。
  • @NathanOliver,也就是说悬空引用和悬空指针也一样?
  • @codekaizer Broken 坏了。使用其中一个,你就有了 UB。
  • @GoswinvonBrederlow,我不仅想了解行为,还想了解语法、类型等。如果你能回答我的问题,我会很高兴。
【解决方案2】:

让我们暂时搁置readData 函数的恶作剧,假设它只是为了示例(并且在您的实际用例中不会产生 UB)。

您不能将具有不同(静态)类型的值直接存储在 std::vector 中。值得注意的是,dataStruct&lt;int&gt;dataStruct&lt;std::string&gt; 是完全不相关的类型,您不能将它们原样存储在同一个向量中。

您的问题归结为“我有以类型不安全的方式提供给我的数据,并希望最终获得对它的类型安全访问”。解决方案是创建一个数据结构,将您的类型不安全的数据解析为该结构。例如,您似乎希望您的示例数据具有结构,因为存在一对 intstd::string(请注意,您的 id%2 没有这样做,因为缺少 else 并且 bool 是再也不会设置为 false,但我猜你希望它交替使用)。

所以让我们把那一堆void* 变成结构化数据:

std::pair<int, std::string> readPair(int pairIndex)
{
  void* ptr;
  std::pair<int, std::string> ret;
  // Copying data here.
  readData(2 * pairIndex + 1, ptr, false);
  ret.first = *reinterpret_cast<int*>(ptr);
  readData(2 * pairIndex + 2, ptr, true);
  ret.second = *reinterpret_cast<std::string*>(ptr);
}

void main()
{
  std::vector<std::pair<int, std::string>> parsedData;
  parsedData.push_back(readPair(0));
  parsedData.push_back(readPair(1));
}

Demo

(为简洁起见,我从 readData() 签名中删除了引用 - 通过将临时表达式存储在变量中可以获得相同的效果。)

一般来说:id 与预期数据类型之间的任何关系都应该转换为数据结构 - 否则只有在知道当前 ID 和此关系时才能推断数据条目的类型,这正是您应该封装在数据结构中的东西。

【讨论】:

  • “让我们搁置一旁……”正是如此。在我的真实案例中,readData(..) 编码良好。这里的示例是模拟在 main(..) 中,void* myData 通过引用传递给函数 (readData),一旦该函数返回,myData 然后指向一个类型安全的 (int,... )。目标是将 myData 及其指向的类型保存到向量中。您的解决方案对我给出的示例非常具体。我期待一种更通用的方法。假设 void* myData 是 readData(void*&data) {..} 的唯一参数。有没有办法将 myData 保存在它指向的类型的向量中?
  • 只有当你知道它指向的类型时。你得到的显然是一个void* 指向内存的某个地方,你只会在那里找到字节。您必须知道这些字节的含义(即它们具有什么类型)才能读取/保存它们,因为该信息未存储在void* 中。重点仍然存在,您不能真正将不同类型的数据存储在同一个向量中(您可以存储void*s,但这无助于您确定类型)。告诉我们如何你会知道readData写的数据类型,我们可以帮助你。
  • 感谢您的回复。所以在我上面给出的 main(..) 函数中,在这一行之后 readData(id, myData, bb);我仍然不知道 myData 指向的类型?我在想,因为 myData 是通过引用 readData 传递的,所以当 readData return myData 已从 void* myData 转换为 int* mydata 时(例如),我可以执行类似 vec.push_back({id, &myData});其中 getType 是(一个虚构的函数)应该返回我 int 作为类型..
  • 确实,readData 无法更改myData 的类型。这种概念甚至在 C++ 中都不存在——你不能通过将变量传递给函数来改变它们的类型(尽管你可以在一定程度上用多态性来模拟它,或者说,std::variant)。写入void* 的数据类型不会存储在任何地方,跟踪它完全是您的责任(并且永远不要尝试将其作为错误类型读取)。这正是 C++ 中要避免使用 void* 的原因(不是双关语)。最好就您的实际问题提出一个更详细的新问题。
【解决方案3】:

您的readData 不是一个有用的功能。任何尝试使用它产生的东西都会产生未定义的行为。

是的,在没有模板的情况下可以大致完成您的要求。要有意义地做到这一点,您有几个选择。 “老派”方式是将数据存储在标记的联合中:

struct tagged_data {
    enum { T_INT, T_STR } tag;
    union {
        int x;
        char *y;
    } data;
};

这使您可以存储字符串或整数,并设置tag 来告诉您特定的tagged_data 项目包含哪一个。然后(至关重要)当您将字符串存储到其中时,您会动态分配它指向的数据,因此在您明确释放数据之前它将保持有效。

不幸的是,(至少在内存服务的情况下)C++11 不支持在联合中存储非 POD 类型,所以如果你走这条路,你必须像上面一样使用char *,而不是一个实际的std::string

消除(大部分)这些限制的一种方法是使用基于继承的模型:

class Data {
public:
    virtual ~Data() { }
};


class StringData : public Data {
    std::string content;
public:
    StringData(std::string const &init) : content(init) {}
};

class IntData : public Data {
    int content;
public:
    IntData(std::string const &init) : content(init) {}
};

这有点不完整,但我认为可能足以给出一般的想法——你会有一个指向基类的指针数组(或向量)。要插入数据,您将创建一个StringDataIntData 对象(动态分配它),然后将其地址存储到Data * 的集合中。当你需要找回一个时,你可以使用dynamic_cast(除其他外)来确定它是从哪个开始的,然后安全地返回到那个类型。都有些丑陋,但确实有效。

即使使用 C++11,您也可以使用基于模板的解决方案。例如,Boost::variant,可以很好地完成这项工作。这将提供重载的构造函数和值语义,因此您可以执行以下操作:

boost::variant<int, std::string> some_object("input string");

换句话说,如果您花费必要的时间和精力来完成上面概述的基于继承的代码,那么您将得到的结果非常好——除了它非常干净,因为它消除了存储指针的要求到基类,使用dynamic_cast 检索正确类型的对象,依此类推。简而言之,这是问题的正确解决方案(直到/除非您可以升级到更新的编译器,并改用 std::variant)。

【讨论】:

  • 让我们在 main(...) 中考虑,从 readData(...) 返回后,void* myData 可以指向更多的内容,而不仅仅是 int 和 string。例如,如果我在 readData 中有一个带有 100 种可能类型 void*&data 的 switch case 可以指向。我会为所有这 100 种类型进行继承吗?没有更通用的方法来解决这个问题吗?
  • @Gaetan:当然有——但如果你想在 C++ 中使用泛型,你可以使用模板。这正是他们的目的。
  • 是的,这就是我写模板结构dataStruct的原因。现在的重点是,如何在这一行 vec.push_back({ id, &myData> });我知道/获取并传递 myData 指向的类型吗?
  • @Gaetan:您通常为每种类型分配一个整数,并存储该整数,因此当您重新读取它时,您可以根据该整数确定其余数据的类型。此处显示的一般样式:stackoverflow.com/q/3741682/179910(以及 litb 的答案也可能非常有用)。
【解决方案4】:

除了 cmets/replies 中描述的给定代码中的问题。 我正在回答你的问题

 vec.push_back(id, &myData<?>); //how can I set the template param to be the type  myData point to? 

在此之前你需要修改vec定义如下

vector<dataStruct<void>> vec;

现在你可以简单地推入向量中的元素

vec.push_back({id, &mydata, bb});

我已尝试修改您的代码以使其正常工作

#include<iostream>
#include<vector>

using namespace std;

template<typename T>
struct dataStruct
{
        int id;
        T** data;
        bool mybool;
};

void readData(int &ID, void*& data, bool& mybool)
{
        if (mybool)
        {
                data = new string("bla");
        }
        else
        {
                int b = 0;
                data = &b;
        }
}

int main ()
{
        void* mydata = nullptr;
        vector<dataStruct<void>> vec;
        bool bb = false;
        for (int id = 0; id < 5; id++)
        {
                if (id%2) bb = true;
                readData(id, mydata, bb);
                vec.push_back({id, &mydata, bb});
        }



  }

【讨论】:

  • 这不能解决问题并且是错误的/没有用的,因为您所做的只是将指向 mydata 的指针存储在向量中。向量中的每个条目都将存储完全相同的data 值,因为&amp;mydata(局部变量mydata 的位置)在循环中永远不会改变。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-01
  • 1970-01-01
  • 2019-08-24
  • 1970-01-01
  • 2011-12-18
  • 1970-01-01
  • 2021-07-19
相关资源
最近更新 更多