【问题标题】:STL container objects by value versus by reference按值与按引用的 STL 容器对象
【发布时间】:2014-11-19 05:13:47
【问题描述】:

我想我对如何移动 STL 容器有一些误解 按值与按引用的对象。具体看不懂 为什么以下程序崩溃:

#include <vector>
#include <set>
#include <cstdio>

class Value {
public:
  int x, y;
  Value(int a, int b) { x = a; y = b; }
};

class Test {
public:
  Test(int x, int y) { values.insert(new Value(x, y)); }
  void add(int x, int y) { values.insert(new Value(x, y)); }
  std::set<Value *> getValues() { return values; }
private:
  std::set<Value *> values;
};

int main() {
  std::vector<Test> Ts;
  for (unsigned i = 0; i < 5; i++) {
    Test t(0, 0);
    t.add(i, 0);
    Ts.push_back(t);
  }
  for (unsigned i = 0; i < 5; i++) {
    for (std::set<Value *>::iterator it = Ts.at(i).getValues().begin(), ite = Ts.at(i).getValues().end(); it != ite; ++it) {
      Value *v = *it;
      printf("(%d, %d) ", v->x, v->y);
    }
    printf("\n");
  }
  return 0;
}

此程序在第二个 for 循环中出现段错误(试图打印 Ts 向量中的值)。但是,如果将初始循环更改为:

for (unsigned i = 0; i < 5; i++) {
  Ts.push_back(Test(0, 0));
}

然后程序运行良好。此外,如果你采取第一个 程序(崩溃)并将打印循环更改为:

for (unsigned i = 0; i < 5; i++) {
  std::set<Value *> values = Ts.at(i).getValues();
  for (std::set<Value *>::iterator it = values.begin(), ite = values.end(); it != ite; ++it) {
    Value *v = *it;
    printf("(%d, %d) ", v->x, v->y);
  }
  printf("\n");
}

那么程序就不会崩溃了。

我想了解导致这些崩溃的原因,以及程序之间的区别。

【问题讨论】:

  • 您知道您的程序正在泄漏内存吗?绝对没有理由在 Test 类中使用 new
  • @pmr 是的。这是我的大型程序的简化测试用例版本,稍后会正确释放值。
  • 除了您泄漏内存的部分之外,您还制作了 for 循环中设置的 values 的多个副本,因为 getValues() 返回一个副本。这可能是迭代器在内存中到达错误位置并导致分段错误的原因。
  • @E_net4 至少,那些迭代器可能不会被比较,它们指向不同的对象(数据结构)。 it 指向的范围的结尾是 not ite

标签: c++ memory-management stl pass-by-reference pass-by-value


【解决方案1】:

我似乎在这里找到的主要问题总结为两行代码:

std::set<Value *> getValues() { return values; }

此成员函数返回您的一组指针的副本。

for (std::set<Value *>::iterator it = Ts.at(i).getValues().begin(), ite = Ts.at(i).getValues().end(); it != ite; ++it) 

使用相同的逻辑线,该指令在初始化阶段创建两个集合。 ite 将不是预期集合的结束迭代器,而是另一个新创建的容器的结束指针。结果是在it 指向内存中的其他意外位置之前,很可能无法到达it != ite

您的更正有效,因为现在您总是在处理来自同一集合的迭代器。这里仍然会发生副本,但在这种情况下它是安全的。另请注意,所有副本都是浅的,因为您存储的是原始指针。

std::set<Value *> values = Ts.at(i).getValues(); // get copy of set
for (std::set<Value *>::iterator it = values.begin(), ite = values.end(); it != ite; ++it) { // using iterators from the same set, this is OK

【讨论】:

  • 谢谢,这是有道理的。你能解释一下为什么第一个“修复”有效吗? IE。不理会循环迭代器,而是改变将项目添加到Ts 向量的方式?只是运气?
  • 我也一直在分析这一点,但没有找到可行的合理理由。唯一的区别似乎是具有 1 个元素而不是 2 个元素的集合。为什么在第一个“修复”中一切正常,看起来仍然像是一个伪装的未定义行为案例。
猜你喜欢
  • 2013-10-27
  • 2021-03-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多