【问题标题】:C++ returning object created on heap and modifying it, doesnt change it?C++返回在堆上创建的对象并修改它,不改变它吗?
【发布时间】:2016-04-03 23:27:36
【问题描述】:

我有以下代码

#include <iostream>
#include <vector>
using namespace std;

struct foo {
    struct bar {
        int foobar;
    };

    vector<bar*> barp;

    bar & insert(int v){
        bar * b = new bar();
        (*b).foobar = v;
        barp.push_back(b);
        return *b;
    }

    void edit(bar & b, int v){
        b.foobar = v;
    }
};

int main() {
    foo f;
    foo::bar b = f.insert(5);
    f.edit(b, 10);
    std::cout << (b.foobar == 10?"true":"false") << std::endl;//now ok
    std::cout << (b.foobar == 5?"true":"false") << std::endl;//now ok
    std::cout << ((*f.barp[0]).foobar == 10?"true":"false") << std::endl;//after edit, still returns false
    std::cout << ((*f.barp[0]).foobar == 5?"true":"false") << std::endl;//after edit, returns true
    return 0;
}

谁能解释一下,为什么“b.foobar”没有变成 10? b和f.barp[0]中保存的地址不一样吗?

编辑:感谢您的回答,添加对 void edit(bar & b, int v) 的引用当然是有道理的。然而,添加对“bar & insert”的引用似乎并没有改变任何东西,因为 (*f.barp[0]).foobar 在编辑后保持不变,即使 insert 现在应该返回引用。

【问题讨论】:

  • 你认为return *b 会做什么?
  • f.barp中保存指针的对象,不是吗?
  • SuhDude555 b 与其地址存储在barp 中的对象不同。想象一下它与任何其他类型:int insert(int v){int *b=new int(); *b = v; barp.push_back(b); return *b;}

标签: c++ pointers c++14 memory-address


【解决方案1】:

b 不是和f.barp[0] 中保存的地址相同吗?

不,它没有。您从插入中按值返回对象,即

bar insert(int v)
^^^

按值返回意味着一个副本:使用复制构造函数构造一个新的bar,丢弃原来的bar*

要获得您期望的结果,请从insert 返回bar&amp;

bar& insert(int v) {
// ^
}

同样,edit 应该通过引用获取其参数,如果您希望所有函数都在同一个对象上工作。

* 如果不是您将指针存储在集合中,这也会造成内存泄漏。您的程序也确实存在内存泄漏,但您可以通过删除 barp 向量中的对象来修复它。

【讨论】:

  • edit 也应该参考。我建议 insert 返回一个指针,作为需要考虑被引用对象的生命周期的自我文档
  • 给定程序中存在内存泄漏。
  • @BarryTheHatchet 确实如此,但 OP 可以通过迭代指针向量并删除它们来修复它。没有向量,指针将永远消失。
  • 感谢您的回答,是否可以在不返回“插入”指针的情况下做到这一点?只是在“插入”上返回引用或对象?稍微修改了我的问题。
  • @SuhDude555 bar&amp; 表示返回引用,而不是指针。 bar* 将是一个指针。
【解决方案2】:

因为您是通过值而不是通过引用将参数传递给edit()

edit() 最终修改了原始对象的副本,然后立即被丢弃。

此外,您还将返回来自insert() 的记录副本。

您的insert() 必须返回一个指针,而不是新记录的副本。您还必须传递一个指向edit() 的指针。

【讨论】:

    【解决方案3】:

    b的地址不是和f.barp[0]中保存的一样吗?

    不,它没有。 每次你使用bar作为参数或返回类型没有进一步的限定,你要求制作一个副本。在这种情况下,这意味着两者main 中的变量bedit 中的变量b 都是副本。

    对于这个玩具示例,您可以通过这些更改使程序按照您的预期进行:

    - bar insert(int v){
    + bar &insert(int v){
    
    - void edit(bar b, int v){
    + void edit(bar &b, int v){
    
    - foo::bar b = f.insert(5);
    + foo::bar &b = f.insert(5);
    

    如果您不明白它的作用或原因,请查阅 C++ 教科书以了解“参考”的解释。

    (如果您以前从未见过上面的-/+ 表示法,它的意思是“将标有减号的每一行替换为标有加号的对应行。”)

    【讨论】:

    • 好。如果您写bar&amp; insert 而不是bar &amp;insert(在其他地方也类似),那会更清楚。将 & 号右对齐是造成新手误解的主要原因,因为&amp;expr 的意思是“地址”而不是“引用类型”。
    • @BarryTheHatchet 我不会那样做。 &amp; 在声明中绑定到右边,所以需要按照我写的方式写,左边有空格。我明白你对 &amp;expr 的意思,但我认为 bar&amp; a, b; 不按照看起来的样子做的潜在混淆更糟
    • 感谢您的回答,我稍微编辑了我的问题,您现在可以看一下吗?很抱歉提出愚蠢的问题。
    • @zwol:什么,那个,单一的,远程的,不寻常的边缘案例?是否比字面上的所有其他用法“差得多”,加上对&amp; 的多种含义的混淆?抱歉,我非常不同意,每天都有关于 SO 的证据。 kera.name/articles/2010/05/… “需要按照我写的方式写” 完全错了。不,它“不需要”以任何这种方式编写。当然不是出于实施原因!
    • @BarryTheHatchet 如果您认为这是一个不寻常的边缘案例,那么您就是一个幸运的灵魂。
    【解决方案4】:
    foo::bar b = f.insert(5);
    

    这个 b 对象与 *f.barp[0] 不同,因为它是根据返回的引用创建的。 为了使这项工作,将此行更改为

    foo::bar& b = f.insert(5);
    

    这样您就创建了对刚刚插入向量中的对象的引用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-09-12
      • 2013-11-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-08-03
      • 2015-04-23
      相关资源
      最近更新 更多