标准::make_unique<T> vs 复位(新 T)



我想问一个关于构造函数内存泄漏的问题。让我们考虑一个类:

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和重置之间没有分歧(新...

你是否发现了异常?捕获异常时,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(...)
{
}

地址清理器也不会报告任何问题。

(P.S. 这是展示鲜为人知的函数尝试块语言特性的好机会。


"为什么记忆不泄露?">

该标准保证如果在构造过程中抛出异常,则使用"new表达式">分配的内存将自动释放。

从 $15.2.5

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


相关问题:

  • "如果构造函数抛出,new分配的内存会发生什么?">

  • "标准::unique_ptr::重置和构造函数异常">

  • "在构造函数中抛出异常是不安全的?">

make_unique

可用于避免在某些情况下的内存泄漏,例如:

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不会提高安全性。不过,它可能仍然更方便。

如果您没有捕获抛出的异常,堆栈可能会也可能不会展开。在多线程程序中,您甚至可以通过让异常从线程中"转义"来触发未定义的行为。简而言之,不要那样做。

更新在 C++17 中,上述bar(unique_ptr<int>(new int(5)), foo());也是异常安全的,因为函数参数的计算不再是未排序的,而是现在不确定的排序。这意味着编译器必须保证有一个顺序,它只是不必告诉你哪个顺序。请参阅巴里对这个问题的回答。

正如在其他回复中提到的,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将按该顺序在构造函数之前调用每个参数。如果第一个参数引发异常,则第二个分配甚至不会发生。

与。unique_ptr::reset我们只是有便利。 由于引发异常的定义new行为,不应看到内存泄漏的任何问题。

相关内容

  • 没有找到相关文章

最新更新