C++ 指针的返回向量-理解

C++ 指针的返回向量-理解,c++,memory-management,pointers,C++,Memory Management,Pointers,我试图理解以下内容(假设MyStorageClass很大): 传递v的内存位置(在类存储中)。但是仍然可以使用=操作符将其副本分配给主类中的向量vm。因此,vm独立于v,如果存储析构函数被称为vm,则不受影响 最后,如果getItems返回了一个引用,那么主要有以下内容: main() { storage s; vector<MyStorageClass*> &vm = s.getItems(); } main() { 存储系统; vector&vm=s.getIt

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

传递
v
的内存位置(在类存储中)。但是仍然可以使用
=
操作符将其副本分配给主类中的向量
vm
。因此,
vm
独立于
v
,如果
存储
析构函数被称为
vm
,则不受影响

最后,如果
getItems
返回了一个引用,那么主要有以下内容:

main()
{
  storage s;
  vector<MyStorageClass*> &vm = s.getItems();
}
main()
{
存储系统;
vector&vm=s.getItems();
}
现在,
vm
持有
v
的地址。因此,它受到存储析构函数的影响


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

是的,你已经正确地理解了这一点。现在,如果你想把它带到下一个层次,首先要找出它不好的原因:

  • 返回类内部的指针或引用会破坏封装
  • 一旦相应的
    s
    -对象被销毁,您得到的引用将成为一个悬空引用,从而打破了引用始终有效的不变性
  • 通过返回指针向量,您会让调用者怀疑是否必须删除这些指针
  • 更好的解决方案是
    storage
    公开从
    s
    返回相应迭代器的
    begin
    end
    方法。或者,对于需要在
    s
    上操作的算法,可以使用-pattern


    此外,在您的例子中,向量
    s
    似乎应该拥有它所包含的对象。这将是一个很好的使用指标。

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

    vector<MyStorageClass*> &vm = s.getItems();
    
    vector&vm=s.getItems();
    
    通过引用获取向量。但是向量包含指针,因此向量超出范围首先不会导致任何析构函数运行——只有当这些析构函数是某种类型的智能指针时,析构函数才会运行(即使是这样也要视情况而定)

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

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

    同样,我认为你的逻辑是好的,你的引用方式是更有效的,但我只是想确保你知道它可以双向工作,没有问题:)(因为它是指针向量,按值也不太坏)


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

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

    但是,如果您需要确保在最后一次访问MyStorageClass对象之前,
    存储
    不会被破坏,那么,在提供的方法中,第三种方法将是首选方法,因为向量数据(即指针)在内存中只存在一次。您应该考虑返回<代码> const 向量,但是,GETIETEMS的每个调用方都可以通过返回引用修改存储类的<代码> V< /代码>向量。

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

    此外,在大型项目中使用指针(并非绝对必要)通常是不受欢迎的。考虑使用智能指针,如<代码> STD::AutoMyPtR <代码>(由于奇怪的复制语义而不是非常推荐),或者更容易理解的<代码> Booo::SyrdYPPTR <代码> />代码> STD:::SyrdYPPTR < /C> > /P>
    作为旁注,第一个和第二个代码示例(至少在大多数现代编译器中)具有完全相同的性能,因为在第一种情况下,编译器可以优化临时返回值(请参阅“返回值优化”).

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

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

    普通指针(
    T*
    )后面没有
    std::vector
    的复制构造函数或赋值运算符(尽管您可以
    main()
    {
      storage s;
      vector<MyStorageClass*> &vm = s.getItems();
    }
    
    vector<MyStorageClass*> &vm = s.getItems();