【问题标题】:Returning vector of pointers - understanding返回指针向量 - 理解
【发布时间】:2011-12-01 13:35:52
【问题描述】:

我正在尝试理解以下内容(假设 MyStorageClass 很大):

class MyStorageClass
{
public:
  string x;
  string y;
  string z;
};

class storage
{
public:
  storage();
  ~storage()
  {
    vector<MyStorageClass *>::iterator it = myVec.begin(); it != myVec.end(); it++)
    {
      delete *it;
    }

  vector<MyStorageClass*> getItems()
  {
    for(int i = 0; i < 10; ++i)
    { 
      v.push_back(new MyStorageClass());
    }
    return v;
  }

private:
  vector<MyStorageClass*> v;

};



main()
{
  storage s;
  vector<MyStorageClass*> vm = s.getItems();
}

据我了解,当s 返回向量并分配给vm 时,这是作为副本(按值)完成的。因此,如果s 超出范围并将其称为析构函数,则vm 拥有自己的副本,并且其结构不受影响。但是,按值传递效率不高。因此,如果您将其更改为通过引用传递:

vector<MyStorageClass*>& getItems()
{
  for(int i = 0; i < 10; ++i)
  { 
    v.push_back(new MyStorageClass());
  }
  return v;
}

您传递了v 的内存位置(在Storage 类中)。但是您仍然使用 = 运算符将其副本分配给 Main 类中的向量 vm。所以,vm 独立于v,如果Storage 析构函数调用vm 不受影响。

最后,如果 getItems 返回了一个引用,并且在 main 中你有以下内容:

main()
{
  storage s;
  vector<MyStorageClass*> &vm = s.getItems();
}

现在,vm 拥有v 的地址。所以它受Storage析构函数的影响。

问题:我上面所说的是真的吗?

【问题讨论】:

  • 听起来对我来说是正确的。不是一个答案,因为我还没有正式验证。
  • 找不到任何问题:)
  • myVec 没有在任何地方声明。您是说~storage 中的v 吗?

标签: c++ memory-management pointers


【解决方案1】:

是的,您已经正确理解了这一点。现在,如果你想更上一层楼,首先要找出不好的原因:

  1. 返回指针或对类内部的引用会破坏封装。
  2. 一旦相应的s-object 被销毁,您获得的引用将成为悬空引用,从而打破引用始终有效的不变量。
  3. 通过返回一个指针向量,让调用者想知道他是否必须删除这些指针。

更好的解决方案是让storage 公开方法beginend,它们从s 返回相应的迭代器。或者,您可以将Visitor-pattern 用于需要在s 上运行的算法。

此外,在您的情况下,向量 s 似乎应该拥有它包含的对象。这将是使用boost::ptr_vector 的一个很好的指标。

【讨论】:

  • +1,因为我第一次丢球时提到悬空:)
【解决方案2】:

即使向量复制它的值,在你的例子中的值是指针,而不是指向的对象。所以“vm 有它自己的副本”并不完全正确:第一段代码中的结果向量将具有 指针 的副本,但没有它们指向的 MyStorageClass 对象的副本到;所以,实际上在所有 3 个代码示例中,如果调用 Storage 析构函数,存储在 vm 中的指针将不再有效!

但是,如果您需要确保 Storage 在最后一次访问 MyStorageClass 对象之前没有被破坏,那么在所提供的方法中,第三种方法将是首选方法,因为向量数据(即指针)仅在内存中一次。您应该考虑返回 const 向量,但是,每个 getItems 调用者都可以通过返回的引用修改 Storage 类的 v 向量。

正如其他人已经指出的那样,首先暴露向量本身可能不是一个好主意;您可以考虑使用迭代器或访问者模式。

此外,指针的使用——不是绝对需要的——在较大的项目中通常是不受欢迎的。考虑使用智能指针,例如std::auto_ptr(虽然由于奇怪的复制语义,不太推荐),或者更容易理解的boost::shared_ptr / std::tr1::shared_ptr

顺便说一句,第一个和第二个代码示例将(至少在大多数现代编译器中)具有完全相同的性能,因为在第一种情况下,编译器可以优化临时返回值(查看“返回价值优化”)。

【讨论】:

    【解决方案3】:

    我认为你所说的是真的,但我对目的有点困惑。

    vector<MyStorageClass*> &vm = s.getItems();
    

    通过引用获取向量。但是向量包含指针,因此超出范围的向量首先不会导致任何析构函数运行 - 只有当这些是某种智能指针时析构函数才会运行(即使那样它也取决于)。

    因此,您可以愉快地按值传递指针向量,而不会造成太大问题。我相信你会通过引用来节省一些效率,但我认为它没有你想象的那么严重。

    此外,您的指向对象是使用 new (动态)创建的,因此您可以以相同的方式按值返回向量,而不必担心丢失指向的对象。

    所以,我再次认为您的逻辑很好,并且您的参考方式更有效,但我只是想确保您知道它可以双向工作而不会出现问题:)(而且因为它是指针向量,按值计算也不错)。

    PS:就参考而言,您确实必须担心悬空问题,这可能比您想象的更令人沮丧,正如上面 Bjorn 所指出的那样。如果您曾经使用过 string.c_str(),那么您可能已经尝到了这一点。如果您从字符串中获取 .c_str() 并且原始字符串超出范围,则 .c_str() 返回的指针是悬空的(指向不再用于该目的的内存)并且访问它会导致未定义的行为.因此,按值可能是更好的选择(但它确实取决于您的设计 - 例如,如果这将是一个在您的应用程序期间持续存在的单例,那么悬空可能不是问题)。

    【讨论】:

      【解决方案4】:

      请注意,如果复制类存储的对象,您的存储类将导致问题。由于您没有提供复制构造函数或赋值运算符,因此将使用默认值。默认会盲目复制指针向量,现在你有两个存储对象都将尝试删除向量中的指针。

      【讨论】:

        【解决方案5】:

        据我了解,当s 返回向量并分配给vm 时,这是作为副本(按值)完成的。因此,如果s 超出范围并将其称为析构函数,vm 将拥有自己的副本并且其结构不受影响。

        普通指针 (T*) 后面没有 std::vector 的复制构造函数或赋值运算符(尽管您可以使用可以复制的智能指针类)。由于没有复制指针指向的内容(“指向”),因此复制操作是浅拷贝。虽然对s.v 的操作不会影响vm(反之亦然),但任何影响从一个访问的指向的操作都会影响另一个,因为它们是相同的。

        如果您要在s.v 而不是MyStorageClass* 中存储一个适当实现的智能指针,标准的std::vector 复制操作可能会产生深拷贝,因此s.v 的内容可以在不影响内容的情况下进行更改vm 以任何方式(反之亦然)。复制指针的复制操作将使用指向类的复制操作来复制指向的对象。因此,每个指向的对象都将仅由一个复制指针指向。

        或者(正如其他人提到的),您可以使用允许共享所有权的智能指针并让它管理指向的对象,从而取消 ~storage 中的 delete 调用。

        但是,按值传递效率不高。

        然而,在某些情况下它是正确的,特别是当改变容器的一个实例不能影响另一个实例时(你提到的一种情况),在这种情况下你不能没有副本就逃脱。正确胜过有效。一般来说,您可以使用copy-swap idiom 来减少由于创建临时工而导致的低效率。据我所知,大多数 STL 实现不使用 std::vector 的复制交换。 Copy-on-write,它只会在向量改变时执行复制,也可以提供帮助。线程使copy-on-write 变得复杂,并可能导致效率低下。

        现在,vm 拥有v 的地址。

        vm 没有地址。虽然引用可能在后台使用指针(它们也可能不使用;根据 C++03 的 §8.3.2-3,它们甚至可能不需要额外的存储空间),但在语言级别的引用不是指针。考虑你可以有空指针,但不能有空引用。更准确的说法是vm 别名为vvmv 指的是同一个对象),这就是为什么对v 的操作会影响vm(反之亦然)。

        【讨论】:

          猜你喜欢
          • 2018-12-13
          • 2010-11-16
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2010-10-13
          • 2013-04-10
          • 1970-01-01
          相关资源
          最近更新 更多