【发布时间】:2019-06-25 09:02:40
【问题描述】:
背景
我有一个像设置节点和边的网络。节点和边都需要是类,在本例中为 Node 或 Arc、as in this question。在我的实际设置中,我正在处理 Node 和 Arc 的相当多的子类。对于内存管理,我使用this answer to the question above。
问题
当构造函数抛出异常时,Windows 上的 Visual Studio 和带有 MinGW 的 g++ 无法捕获它,但会在没有错误处理的情况下退出(g++/MinGW 报告 SIGTRAP 信号),而 Linux 上的 g++ 和 clang++ 会正确处理异常。如果 Arc 没有例外地创建 Arc(n1, n2, false),则所有编译器都可以正常工作。在所有情况下,都没有相关的编译器警告(使用 /W4 和 -Wall)有人可以解释一下,为什么这在 Windows 上不起作用?或者甚至给出一个解决方法?
代码
#include <iostream>
#include <stdexcept>
#include <vector>
#include <memory>
struct Node;
struct Arc {
Node *left,*right;
private:
// shared pointer to self, manages the lifetime.
std::shared_ptr<Arc> skyhook{this};
public:
// c'tor of Arc, registers Arc with its nodes (as weak pointers of skyhook)
explicit Arc(Node* a_, Node* b_, bool throw_exc);
// resets skyhook to kill it self
void free() {
std::cout << " Arc::free();\n" << std::flush;
skyhook.reset();
}
virtual ~Arc() {
std::cout << " Arc::~Arc();\n" << std::flush;
}
};
struct Node {
explicit Node() {
std::cout << " Node::Node()\n" << std::flush;
}
std::vector<std::weak_ptr<Arc> > arcs;
~Node() {
std::cout << " Node::~Node();\n" << std::flush;
for(const auto &w : arcs) {
if(const auto a=w.lock()) {
a->free();
}
}
}
};
Arc::Arc(Node *a_, Node *b_, bool throw_exc) : left(a_), right(b_) {
std::cout << " Arc::Arc()\n" << std::flush;
if (throw_exc) {
throw std::runtime_error("throw in Arc::Arc(...)");
}
a_->arcs.push_back(skyhook);
b_->arcs.push_back(skyhook);
}
int main(int argc, char* argv[]) {
std::cout << "n1=new Node()\n" << std::flush;
Node *n1 = new Node();
std::cout << "n2=new Node()\n" << std::flush;
Node *n2 = new Node();
std::cout << "try a=new Arc()\n" << std::flush;
try {
Arc *a = new Arc(n1, n2, true);
} catch (const std::runtime_error &e) {
std::cout << "Failed to build Arc: " << e.what() << "\n" << std::flush;
}
std::cout << "delete n1\n" << std::flush;
delete n1;
std::cout << "delete n2\n" << std::flush;
delete n2;
}
输出
这是我在 Linux 和 Windows 上都得到的结果
n1=new Node()
Node::Node()
n2=new Node()
Node::Node()
try a=new Arc()
Arc::Arc()
在 Linux 上使用 g++(7.4.0 和 8.3.0)或 clang++(6.0.0)...
它按预期工作:
Arc::~Arc();
Failed to build Arc: throw in Arc::Arc(...)
delete n1
Node::~Node();
delete n2
Node::~Node();
使用 VC++ (2017) ...
它坏了
Arc::~Arc()
并且运行以退出代码 -1073740940 (0xC0000374) 终止
使用 g++ (9.1.0) MinGW 7.0
中断,但报告信号
Signal: SIGTRAP (Trace/breakpoint trap)
Arc::~Arc();
并以退出代码 1 结束
【问题讨论】:
-
您使用的是哪个exception handling model?
-
异常处理模型为/EHsc
-
在构造函数中加入try/catch可以处理异常。在 vc++2019 上测试。
-
@user2181624:请注意,如果构造函数通过异常退出,所有构造的成员和基(当然还有局部变量)都将被销毁,即使(对于非委托构造函数)类的析构函数不被调用——因此通常可以避免在构造函数中进行任何显式异常处理。
-
@DavisHerring:我认为它仍然非常脆弱,因为 1)移动/复制必须小心处理(这里不是),2)在堆栈上创建实例将是语法上可能但近乎保证的未定义行为(除非手动调用
free),并且3)执行auto* p = new Arc(...); delete p;也保证UB(双重免费,如果free未被调用)。这是一个巨大的脚枪。
标签: c++ exception visual-c++ g++ mingw