在函数内new一个对象,如果作为引用返回,是不是就可以不用delete了?

背景大概是这样的: 一个外部函数,它的目的是产生并返回一个新的对象(不管它的形式是对象,指针还是引用)。 获得了这个对象后,可以在外部操作它,对此我有…
关注者
125
被浏览
50,895

20 个回答

没邀请,厚脸皮强答。

先回答你提到的问题。只是我得先声明一下,就此例子来看,你这几个问题都有些“有问题,我们要解决问题;没问题,我们也要创造出问题然后解决它。”给搞的。我真正想抢答的是后面几点。那些可以帮到C++初学者。

问题一:返回引用不用删除?会自动删除?

原代码:
IntPair& operator*(const IntPair& lhs, const IntPair& rhs) {
	int x = lhs.x * rhs.x;
	int y = lhs.y * rhs.y;
	IntPair* r = new IntPair(x, y);
	return *r;
int main() {
009	IntPair r = IntPair(1, 2) * IntPair(2, 3);	
	return(0);
}

没有自动删除。你看到的三个析构调用,其中有一个是代码中标为 009 行定义的r的析构。你返回引用,可是你没有用引用来"接"这个对象。请对比:

IntPair r = IntPair(1, 2) * IntPair(2, 3);	
IntPair&  rr = IntPair(1, 2) * IntPair(2, 3);	

r新构建了一个对象,然后复制了函数返回的引用的值 。rr才是引用。这道理和函数返回指针,你却不用指针接,非要这样:

IntPair r = * (IntPair(1, 2) * IntPair(2, 3));

此时,r是个新对象。(重载的 * )函数返回指针同样没有被释放。而你显然也不能尝试释放 r 。

这个问题你后来发现了? 所以干脆先插一句话:想观察对象的生命周期,请匹配地在它构造函数和析构函数里打印信息。 比如本例,如果你在构造里也打印信息,那你就很容易发现构4析3了。

问题二:函数内创建的堆对象,返回什么好?

答:如果确实需要函数创建(需要释放)堆对象,那就返回 std::unique_ptr<T>吧。不用你IntPair对象了,就以一个int* 为例:

#include <iostream>
#include <memory>
std::unique_ptr<int> create(int value)
    int* p = new int(value);
    return std::unique_ptr<int>(p);
int main(int argc, char **argv)
    std::unique_ptr<int> p = create(99);
    std::cout << *p << std::endl;  //后面也不用手工delete
}

C++ 14或17?会有make_unique()函数,所以到时上面create()内的new关键字可以不出现了,这会让追求new 和 delete 完美匹配的处女座舒服点,如:

return std::make_unique<int>(value);

如果在多线程环境下,并且明确知道所返回的堆对象会被多线程并发访问,那就用std::shared_ptr<T>代替std::unique_ptr<T>。这时make_shared()已经是现成标准。

当然,会有时候,我们写create()时,我们可以肯定知道的是,这家伙返回后,需要释放 ;却不能确切知道返回的对象会不会被多线程使用,此时还是回归简单,就返回更基础,更简单,更环保,更傻瓜的 unique_ptr。让调用者(也是知情者)去负责考虑要不要转换成shared_ptr。

=$=$=$=$=

我想谈问题是:

在函数内返回堆对象?要守规矩。

生孩子是一种非常稀奇的事吗?当然不是,全世界随时,到处都有人生娃。但生孩子是一个人一生中的常见活动或行为吗?显然也不是。

C++代码中出现在函数体中直接new一个对象并返回的机率,和生娃没差多少。

就说你举的例子。我们知道,在C++中执行 3 * 2,肯定不会返回一个需要你释放的指针。所以两个IntPair对象执行相乘操作,却设计出返回一个需要释放的指针,这不是接口不友好,让调用者纠结、猜测、迷惘甚至失去生活的信心,简直反人类的设计。

所以,就应该大大方方地返回没有释放需求的栈对象。C++相比像Java等其它语言,是没有GC,可是C++有明确自动回收栈对象啊。回收及时,回收时机完全可控。回收还速度超过堆内存……如果不用它,非要用堆对象,然后再扯出Java:

说起来,Java直接返回了一个引用(是吧?),对象的内容就不会随着函数的结束而释放,进而也就不需要copy了,那它和前两种方式哪种比较像?

有些不 厚道。 :)

//直接返回栈对象才人道:
IntPair operator*(const IntPair& lhs, const IntPair& rhs) 
        return IntPair(lhs.x * rhs.x,  lhs.y * rhs.y);
}

马上有人说,那要复制一次临时效率多低啊?其实不会。编译器(估计在10年前)就会帮优化这样的c返回了。如果对象内部了确实又复杂又大,可以考虑定制 IntPair 的 “move construct”行为。

什么情况下,确实需要从函数返回新的堆对象? 简单说:你想都不用想地,直觉地为某个函数命名为 CreateXXX 或 NewXXX()时。

首先,第三种是错误的。你创建一个局部变量然后返回它的引用,你明明知道这是返回的已经退栈的局部变量了,程序今天没崩溃只是未做检查的侥幸而已。

第二种,你内存泄露了。实际上你产生了四个实例,两个作为乘数,一个是你new的,一个是你在main函数栈里的。new的那个你没有delete,因此内存泄露

第一种是正确的。

————————————————————————————————————

有朋友质疑说,第三种方法是正确的(认为可以让一个函数返回局部变量的引用,然后外面用一个拷贝构造来完成数据的return)。我们暂且不讨论脱离作用域这种问题,我们只分析函数return的时候,将离开它的作用域,这个object将被析构,如此返回得到的东西,还是你原来希望的吗?我们来看下面的简单例子:

#include <iostream>
using namespace std;
class Foo {
public:
    int value;
    ~Foo(){
        // Destruct the object
        this->value = 0;
        cout << "deleting Foo" << endl;
Foo & getFoo(){
    Foo f = Foo();
    // here user want to set something
    f.value = 33;
    cout << f.value << endl;
    return f;
int main() {
    Foo f2 = getFoo();
    // the return value is not as expected