会发生什么情况,在下面的代码,如果一些数组元素的建设/销毁抛出?
X* x = new X[10]; // (1)
delete[] x; // (2)
我知道,内存泄漏被阻止,但额外的:
广告(1)中,先前构造的元件破坏? 如果是的话,如果析构函数在这种情况下抛出,会发生什么?
广告(2),是在还未遭到破坏元素析构? 如果是的话,如果析构函数再次抛出,会发生什么?
会发生什么情况,在下面的代码,如果一些数组元素的建设/销毁抛出?
X* x = new X[10]; // (1)
delete[] x; // (2)
我知道,内存泄漏被阻止,但额外的:
广告(1)中,先前构造的元件破坏? 如果是的话,如果析构函数在这种情况下抛出,会发生什么?
广告(2),是在还未遭到破坏元素析构? 如果是的话,如果析构函数再次抛出,会发生什么?
是的,如果的构造x[5]
引发,然后五个数组元素x[0]..x[4]
已经成功地构建将被正确地破坏。
std::terminate
立即调用。 这就是为什么析构函数不应该抛出。 这里有两个相互排斥的选项:
如果达到标签(2)
构造没扔。 也就是说,如果x
已成功创建,所有十个元素构建成功。 在这种情况下,是的,他们都被删除。 不,你的析构函数仍然不应该扔。
如果构造通过一步抛出部分路(1)
那么阵列x
根本不存在 。 语言试图为您创建它,失败,并抛出一个异常-所以你没有达到(2)
在所有。
要理解的关键问题是, x
要么存在-在一个健全的和可预见的状态-要么没有。
语言不给你一些未使用的半初始化的事情,如果构造失败,因为你不能用它做什么呢。 (你甚至不能安全地删除它,因为就没有办法来跟踪元素的构建,并且只是随机的垃圾)。
这可能有助于考虑阵列与十个数据成员的对象。 如果你正在构建这样一个类的实例,以及基类或成员的构造函数之一抛出,所有以前建成基地和成员被破坏以完全相同的方式和你的对象永远不会启动现有的。
我们可以用下面的代码进行测试:
#include <iostream>
//`Basic` was borrowed from some general-purpose code I use for testing various issues
//relating to object construction/assignment
struct Basic {
Basic() {
std::cout << "Default-Constructor" << std::endl;
static int val = 0;
if(val++ == 5) throw std::runtime_error("Oops!");
}
Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; }
Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; }
Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; }
Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; }
~Basic() noexcept { std::cout << "Destructor" << std::endl; }
};
int main() {
Basic * ptrs = new Basic[10];
delete[] ptrs;
return 0;
}
该代码产生崩溃之前的输出如下:
Default-Constructor
Default-Constructor
Default-Constructor
Default-Constructor
Default-Constructor
Default-Constructor
[std::runtime_error thrown and uncaught here]
请注意,在任何时候都析构函数调用。 这不一定是一个关键的东西,因为未捕获的异常将反正程序崩溃。 但是,如果我们捕获错误,我们看到一些令人欣慰的:
int main() {
try {
Basic * ptrs = new Basic[10];
delete[] ptrs;
} catch (std::runtime_error const& e) {std::cerr << e.what() << std::endl;}
return 0;
}
输出更改为:
Default-Constructor
Default-Constructor
Default-Constructor
Default-Constructor
Default-Constructor
Default-Constructor
Destructor
Destructor
Destructor
Destructor
Destructor
Oops!
所以析构函数会被自动调用完全构造的对象,即使没有明确的delete[]
调用,因为new[]
呼叫处理机制来处理这个问题。
但是,你不用担心的是第六对象:在我们的例子中,由于Basic
没有做任何资源管理(和精心设计的计划不会有Basic
做资源管理,如果它的构造可能抛出这样),我们不“T担心。 但是,我们可能会担心,如果我们的代码看起来像这个:
#include <iostream>
struct Basic {
Basic() { std::cout << "Default-Constructor" << std::endl; }
Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; }
Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; }
Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; }
Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; }
~Basic() noexcept { std::cout << "Destructor" << std::endl; }
};
class Wrapper {
Basic * ptr;
public:
Wrapper() : ptr(new Basic) {
std::cout << "WRDefault-Constructor" << std::endl;
static int val = 0;
if(val++ == 5) throw std::runtime_error("Oops!");
}
Wrapper(Wrapper const&) = delete; //Disabling Copy/Move for simplicity
~Wrapper() noexcept { delete ptr; std::cout << "WRDestructor" << std::endl; }
};
int main() {
try {
Wrapper * ptrs = new Wrapper[10];
delete[] ptrs;
} catch (std::runtime_error const& e) {std::cout << e.what() << std::endl;}
return 0;
}
在这里,我们得到如下的输出:
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
WRDestructor
Oops!
的大块Wrapper
对象不会出现内存泄漏,但第六Wrapper
对象会泄漏一个Basic
对象,因为它没有正确清理!
幸运的是,通常任何的资源管理方案的情况下,所有这些问题消失,如果你使用智能指针:
#include <iostream>
#include<memory>
struct Basic {
Basic() { std::cout << "Default-Constructor" << std::endl; }
Basic(Basic const&) { std::cout << "Copy-Constructor" << std::endl; }
Basic(Basic &&) { std::cout << "Move-Constructor" << std::endl; }
Basic & operator=(Basic const&) { std::cout << "Copy-Assignment" << std::endl; return *this; }
Basic & operator=(Basic &&) { std::cout << "Move-Assignment" << std::endl; return *this; }
~Basic() noexcept { std::cout << "Destructor" << std::endl; }
};
class Wrapper {
std::unique_ptr<Basic> ptr;
public:
Wrapper() : ptr(new Basic) {
std::cout << "WRDefault-Constructor" << std::endl;
static int val = 0;
if(val++ == 5) throw std::runtime_error("Oops!");
}
//Wrapper(Wrapper const&) = delete; //Copy disabled by default, move enabled by default
~Wrapper() noexcept { std::cout << "WRDestructor" << std::endl; }
};
int main() {
try {
std::unique_ptr<Wrapper[]> ptrs{new Wrapper[10]}; //Or std::make_unique
} catch (std::runtime_error const& e) {std::cout << e.what() << std::endl;}
return 0;
}
和输出:
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Default-Constructor
WRDefault-Constructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
WRDestructor
Destructor
Oops!
需要注意的是呼叫的数量来Destructor
现在匹配调用次数Default-Constructor
,它告诉我们的Basic
目标现在得到妥善清理。 而且由于该资源管理Wrapper
在做已经委托给unique_ptr
对象,事实上,第六Wrapper
对象没有其删除器叫做不再是一个问题。
现在,很多的,这涉及strawmanned代码:没有合理的程序员将永远有一个资源管理器throw
不正确的处理代码,即使是使用智能指针由“安全”。 但是,一些程序员只是没有合理的,即使是这样,这是可能的,你可能会遇到,你必须编写代码,这样一个奇怪的,充满异国情调的场景。 这个教训,那么,就我而言,就是要始终使用智能指针和其他STL对象来管理动态内存。 不要试图推出自己的。 它可以节省尝试调试的事情,当你头疼完全一样。