在C++中,使用引用而不是指针能够解决内存泄漏问题吗?

11

大多数内存泄漏出现在对象指针返回时程序员忘记删除它的情况下。

例如:

class my_class
{
  ...
};

my_class* func1()
{
  my_class* c = new my_class;
  return c;
}

int main()
{
  my_class* var1 = func1();
  ...
  // Programmer forgot delete the var1: delete var1;
  // -- or --
  // Doesn't know 'delete[] var1;' is correct or 'delete var1;'.
}

有些内存泄漏是由于程序员创建了一个对象的指针,但忘记将其删除。

例如:

class my_class
{
  ...
};

void func2(my_class* p)
{
  ...
}

int main()
{
  my_class* var3 = new my_class;

  func2(var3);

  // Does func2 deletes var3? Programmer doesn't know.
  // -- or --
  // Programmer forgot delete the var3.
}

我使用一种方法来解决内存泄漏问题,但在复杂情况下我不确定是否有效。

我的方法是:除了一个地方外,不要使用任何指针,而是使用引用代替指针。

例如:

class my_class
{
  ...
};

my_class& func1()
{
  my_class* c = new my_class; // except one place.
  return *c;
}

void func2(my_class& p)
{
  ...
}

int main()
{
  my_class& var1 = func1();
  my_class  var2 = func1();

  my_class var3;
  func2(var3);

  // There is nothing to forget.
}

使用引用而不是指针,能够解决内存泄漏问题吗?

这是解决内存泄漏的好方法吗,还是有更好的方法?


编辑:

对于这个问题的某些回答认为下面的代码存在内存泄漏。

因为这是一个新问题,我将它单独提出来了。

class my_class
{
  ...
};

my_class& func()
{
  my_class* c = new my_class;
  return *c;
}

int main()
{
  my_class& var1 = func();

  // I think there is no memory leak.
}

我在这里提问:这段代码是否存在内存泄漏?(包含引用、new,但没有delete)


请了解boost智能指针,以获得更高层次的内存泄漏解决方案。 - Michael Dorgan
5
除了你分配了但没有释放的内存导致内存泄漏这一事实外,其实没有什么需要忘记的。噢,还有可能会忘记哪些函数返回对象引用需要在之后删除,以及哪些函数返回在其他地方进行管理的对象引用。 - David Rodríguez - dribeas
我仍然不完全相信 my_class* c = new my_class; return *c; 不会导致内存泄漏。当(在这种情况下)var2超出范围时,析构函数可能会被调用,但是内存可能不会被释放。 - Chris Frederick
@Chris:因为函数的返回类型是引用,所以不会发生内存泄漏。 - Amir Saniyan
1
Amir:看看@Benjamin Lindley的回答。 - Chris Frederick
5个回答

9
你还没有解决任何内存泄漏问题。如果你使用了 new,那么就必须使用 delete。你所做的只是取消指针的引用,它仍然需要被删除。你可以通过创建局部对象并按值返回或使用智能指针来解决内存泄漏问题。99 次中有 100 次,我更喜欢按值返回的选项。
现在,像许多初学者一样,按值返回大型对象的想法可能会让你担心性能问题。阅读this以消除你的担忧。

6

你的方法完全没有帮助:

Foo & magic()
{
  return * new Foo();  // dynamically allocated
}

int main()
{
  Foo x = magic();     // copied and lost
  Foo & y = magic();   // reference, still holding on
  delete &y;           // phew, and ewww
}

你仍然只是动态分配对象,需要手动管理它!事实上,我的第一个用法会复制引用,然后忘记引用,从而创建即时泄漏!即使你仍然保留引用,就像第二个例子一样,它也变得完全不可管理!(请参见Soap的评论。)
因此,请快速忘记这个想法,转而看看资源管理容器!
例如:
#include <memory>

typedef std::shared_ptr<Foo> FooPtr;

FooPtr makeFoo()
{
  // return FooPtr(new Foo);       // Baby's first smart pointer
  return std::make_shared<Foo>();  // Adult approach to shared_ptr
}

int main()
{
  FooPtr pf = makeFoo();

  someFooModifier(*pf);
}

2
无论如何也不要写“delete &variable”,即使在这种情况下它起作用。 - SoapBox
1
你永远不应该像原帖作者那样做!(不过我添加了一条注释,谢谢。) - Kerrek SB

3
不要从函数中返回原始指针;将它们放入智能指针类,例如unique_ptrshared_ptr。这样你就不必担心删除已分配的对象。
另外,在您的第二个示例中,谁负责删除由func1()分配的对象?仅仅因为您返回引用而不是指针,并不意味着自动释放已分配的内存。

2

是的,存在内存泄漏。以下代码将使用valgrind执行:

#include <iostream>

class my_class
{
    public :
    my_class(void)
    {
        std::cout << "Constructor" << std::endl;
    }
     ~my_class(void)
    {
        std::cout << "Destructor" << std::endl;
    }
};

my_class& func(void)
{
    my_class* c = new my_class;
    return *c;
}

int main(void)
{
    my_class& var1 = func();
    // Question : I think there is no memory leak.
    // Answer : Yes there is a memory leak in func().
}



/shared/TOOLS/valgrind-3.6.1/bin/valgrind --leak-check=full ./main2.exe
==13004== Memcheck, a memory error detector
==13004== Copyright (C) 2002-2010, and GNU GPL'd, by Julian Seward et al.
==13004== Using Valgrind-3.6.1 and LibVEX; rerun with -h for copyright info
==13004== Command: ./main2.exe
==13004==
Constructor
==13004==
==13004== HEAP SUMMARY:
==13004==     in use at exit: 1 bytes in 1 blocks
==13004==   total heap usage: 1 allocs, 0 frees, 1 bytes allocated
==13004==
==13004== 1 bytes in 1 blocks are definitely lost in loss record 1 of 1
==13004==    at 0x4A06DC7: operator new(unsigned long) (vg_replace_malloc.c:261)
==13004==    by 0x400966: func() (in /home/toto/CppTest/main2.exe)
==13004==    by 0x4009BC: main (in /home/toto/CppTest/main2.exe)
==13004==
==13004== LEAK SUMMARY:
==13004==    definitely lost: 1 bytes in 1 blocks
==13004==    indirectly lost: 0 bytes in 0 blocks
==13004==      possibly lost: 0 bytes in 0 blocks
==13004==    still reachable: 0 bytes in 0 blocks
==13004==         suppressed: 0 bytes in 0 blocks
==13004==
==13004== For counts of detected and suppressed errors, rerun with: -v
==13004== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 7 from 7)

1

你不应该用引用替换拥有指针(负责删除的指针);惯用法是假定引用永远不会拥有所引用的资源。

my_class& func1()
{
    my_class* c = new my_class;
    return *c;
}

int main()
{
    my_class& ref = func1();
    // MUST delete, but this is ugly!
    delete &ref;
}

相反,用智能指针替换拥有指针: std::unique_ptr func1() { return std::unique_ptr(new my_class); }

int main()
{
    auto p = func1();
    // You gain exception-safety for free
}

你说得对,非拥有指针可以被引用替代。对于大多数情况,这是推荐的做法(更多信息请参见末尾链接)。

// Won't have to check for 0 in the body
void
my_func1(my_class&);

std::unique_ptr<my_class>
func1()
{
    return std::unique_ptr<my_class>(new my_class);
}

int main()
{
    auto p = func1();

    func2(*p);
}

这里有一个关于原始指针和智能指针使用差异的问题。在我的一个回答中,我提到了原始指针与引用的不同用例。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接