std :: make_unique <t> vs reset(新T)

时间:2016-12-06 09:16:47

标签: c++ c++11 memory-leaks smart-pointers unique-ptr

我想问一个关于构造函数中的内存泄漏的问题。我们来考虑一个班级:

 class Foo
 {
   public:
      Foo(){ throw 500;} 
 };

之间有什么区别
std::unique_ptr<Foo> l_ptr = std::make_unique<Foo>();

std::unique_ptr<Foo> l_ptr;
l_ptr.reset(new Foo());

在我看来,使用make_unique的解决方案可以保护我免受内存泄漏的影响,但在这两种情况下我都得到了相同的valgrind结果:

$ valgrind --leak-check=full ./a.out
==17611== Memcheck, a memory error detector
==17611== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==17611== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==17611== Command: ./a.out
==17611== 
terminate called after throwing an instance of 'int'
==17611== 
==17611== Process terminating with default action of signal 6 (SIGABRT)
==17611==    at 0x5407418: raise (raise.c:54)
==17611==    by 0x5409019: abort (abort.c:89)
==17611==    by 0x4EC984C: __gnu_cxx::__verbose_terminate_handler() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==17611==    by 0x4EC76B5: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==17611==    by 0x4EC7700: std::terminate() (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==17611==    by 0x4EC7918: __cxa_throw (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==17611==    by 0x40097B: Foo::Foo() (in /home/rungo/Repositories/test/a.out)
==17611==    by 0x4008DC: main (in /home/rungo/Repositories/test/a.out)
==17611== 
==17611== HEAP SUMMARY:
==17611==     in use at exit: 72,837 bytes in 3 blocks
==17611==   total heap usage: 4 allocs, 1 frees, 72,841 bytes allocated
==17611== 
==17611== 132 bytes in 1 blocks are possibly lost in loss record 2 of 3
==17611==    at 0x4C2DB8F: malloc (in /usr/lib/valgrind/vgpreload_memcheck-amd64-linux.so)
==17611==    by 0x4EC641F: __cxa_allocate_exception (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
==17611==    by 0x400963: Foo::Foo() (in /home/rungo/Repositories/test/a.out)
==17611==    by 0x4008DC: main (in /home/rungo/Repositories/test/a.out)
==17611== 
==17611== LEAK SUMMARY:
==17611==    definitely lost: 0 bytes in 0 blocks
==17611==    indirectly lost: 0 bytes in 0 blocks
==17611==      possibly lost: 132 bytes in 1 blocks
==17611==    still reachable: 72,705 bytes in 2 blocks
==17611==         suppressed: 0 bytes in 0 blocks
==17611== Reachable blocks (those to which a pointer was found) are not shown.
==17611== To see them, rerun with: --leak-check=full --show-leak-kinds=all
==17611== 
==17611== For counts of detected and suppressed errors, rerun with: -v
==17611== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
[1]    17611 abort (core dumped)  valgrind --leak-check=full ./a.out

当我使用clang ++和g ++时,它是一样的。 我在这里找到:https://isocpp.org/wiki/faq/exceptions#ctors-can-throw这句话:

  

注意:如果构造函数通过抛出异常结束,则清除与对象本身关联的内存 - 没有内存泄漏。

我的问题是为什么我们在这种情况下有泄漏以及为什么make_unique不能防止泄漏(doeas意味着make_unique和reset之间没有dofference(new ...)?

3 个答案:

答案 0 :(得分:9)

你是否正在捕捉异常?捕获异常时,valgrind (使用 g ++ 6.2 -g进行编译)检测到make_uniquereset都没有泄漏。

int main() try
{
#if TEST_MAKE_UNIQUE
    std::unique_ptr<Foo> l_ptr = std::make_unique<Foo>();   
#else
    std::unique_ptr<Foo> l_ptr;
    l_ptr.reset(new Foo());
#endif
}
catch(...)
{

}

AddressSanitizer 也不会报告任何问题。

(P.S。这是展示鲜为人知的function-try-block语言功能的好机会。)

  

&#34;为什么内存不会被泄露?&#34;

该标准保证在构造过程中抛出异常时,将自动释放分配有&#34; new表达式&#34; 的内存。

From $15.2.5:

  

如果对象是由new-expression([expr.new])分配的,则调用匹配的释放函数([basic.stc.dynamic.deallocation])(如果有)以释放对象占用的存储空间

相关问题:

答案 1 :(得分:3)

在某些情况下,

int foo(); void bar(unique_ptr<int> a, int b); int main() { try { bar(unique_ptr<int>(new int(5)), foo()); } catch (...) {/*TODO*/} } 可用于避免内存泄漏,例如:

new

此处可能首先调用foo(),然后调用unique_ptr<int>可能会在foo()构建之前发生。如果int抛出,则make_unique将被泄露。如果使用int foo(); void bar(unique_ptr<int> a, int b); int main() { try { bar(make_unique<int>(5), foo()); } catch (...) {/*TODO*/} } ,则无法执行此操作:

foo()

在这种情况下,首先调用int,如果抛出则不创建make_unique<int>(5),或者首先调用foo(),并允许完成。如果int然后抛出,则在通过临时unique_ptr<int>的析构函数进行堆栈展开时将删除l_ptr.reset(new Foo());

如果你没有把任何其他东西扔进同一个语句中,就像调用make_unique一样,那么bar(unique_ptr<int>(new int(5)), foo());就不会提高安全性。不过,它可能仍然更方便。

如果你没有捕获抛出的异常,堆栈可能会或可能不会解开。在多线程程序中,您甚至可以通过让异常&#34; escape&#34;来触发未定义的行为。从一个线程。简而言之,不要这样做。

UPDATE 在C ++ 17中,上面的public class Hello{ public static void main(String ...args){ String str = "a3tx2z"; StringBuilder stri = new StringBuilder(); for(int i = 0; i<str.length();i++){ if(Character.isDigit(str.charAt(i))){ int a = Character.getNumericValue(str.charAt(i)); char b = str.charAt(i+1); if(a == 0){ i++; }else{ for(int j = 0;j<a-1;j++){ stri.append(b); } } }else{ stri.append(str.charAt(i)); } } str = stri.toString(); System.out.println(str); } } 也是异常安全的,因为函数参数的评估不再是未排序的,而是现在不确定地排序。这意味着编译器必须保证有订单,它不必告诉你哪个订单。请参阅Barry对this question的回复。

答案 2 :(得分:1)

正如其他回复中提到的那样,valgrind抱怨你因为有一个未被捕获的异常而泄漏记忆,而异常则会调用std::terminate,这反过来又会让所有内容保持原样。如果可执行文件没有终止(例如,你在某处捕获了异常),那么内存将由语言对new行为的定义自动释放。

您似乎在问一个关于为什么std::make_unique存在的更深层次的问题。这适用于以下情况:

some_func(new Foo, new Foo);

在这种情况下,该语言无法保证何时调用new与调用Foo::Foo()时。您可能让编译器稍微组织一些事情,以便new被调用两次,一次为第一个参数分配空间,一次为第二个参数分配空间。然后构造Foo(),抛出异常。第一个分配被清除,但第二个分配将泄露,因为没有构造(或那里的例外)!队列make_unique

some_func(std::make_unique<Foo>(), std::make_unique<Foo>());

现在我们正在调用一个函数,new将在每个参数的构造函数之前按顺序调用。如果第一个参数抛出异常,则第二个分配赢了'甚至发生了。

Vs以上。 unique_ptr::reset我们只是方便。由于定义了new抛出异常的行为,您不应该看到任何内存泄漏问题。