当网络边缘的构造函数抛出时避免 SIGTRAP
Avoid SIGTRAP when the constructor of a network edge throws
背景
我有一个类似设置节点和边的网络。节点和边都需要 类,在本例中为 Node
或 Arc
、. In my real setup I am dealing with quite a number of subclasses of both Node and Arc. For memory management, I use 。
问题
当构造函数抛出异常时,Visual Studio 和g++ with MinGW on Windows 无法捕捉到它,但没有错误处理退出(g++/MinGW 报告SIGTRAP 信号),而g++ and clang++ on Linux 正确处理异常。如果 Arc 无一例外地创建 Arc(n1, n2, false)
,所有编译器都可以正常工作。在所有情况下,都没有相关的编译器警告(使用 /W4 resp.-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 结束
这里似乎使用了 std::shared_ptr
以避免考虑生命周期和所有权,这会导致代码不佳。
更好的设计是有一个 class,比如 Network
,它拥有 Node
和 Arc
并将它们存储在 std::list
中。这样您就不需要 std::shared_ptr
或 std::week_ptr
以及使用它们产生的复杂代码。 Node
s 和 Arc
s 只能使用指向彼此的普通指针。
示例:
#include <list>
#include <vector>
#include <cstdio>
struct Node;
struct Arc {
Node *left, *right;
};
struct Node {
std::vector<Arc*> arcs;
};
class Network {
std::list<Node> nodes;
std::list<Arc> arcs;
public:
Node* createNode() {
return &*nodes.emplace(nodes.end());
}
Arc* createArc(Node* left, Node* right) {
Arc* arc = &*arcs.emplace(arcs.end(), Arc{left, right});
left->arcs.push_back(arc);
right->arcs.push_back(arc);
return arc;
}
};
int main() {
Network network;
Node* a = network.createNode();
Node* b = network.createNode();
Arc* ab = network.createArc(a, b);
std::printf("%p %p %p\n", a, b, ab);
}
tl;dr: 继承自 std::enable_shared_from_this
并使用 weak_from_this()
.
考虑以下结构,它与您的 (https://godbolt.org/z/vHh3ME) 相似:
struct thing
{
std::shared_ptr<thing> self{this};
thing()
{
throw std::exception();
}
};
在抛出异常时对象 *this
和 self
的状态是什么,哪些析构函数将作为堆栈展开的一部分执行?对象本身还没有完成构造,因此 ~thing()
不会(也不能)被执行。另一方面,self
是 完全构造的(成员在进入构造函数体之前被初始化)。因此,~std::shared_ptr<thing>()
将 执行,这将在未完全构造的对象上调用 ~thing()
。
从 std::enable_shared_from_this
继承不会出现此问题,假设在构造函数完成执行 and/or throws 之前没有创建实际的 shared_ptr
(weak_from_this()
将是您的朋友) ), 因为它只包含一个 std::weak_ptr
(https://godbolt.org/z/TGiw2Z); neither does a variant where your shared_ptr
is initialized at the end of the constructor (https://godbolt.org/z/0MkwUa),但是在你的案例中合并这并不容易,因为你给出了 shared/weak 指针 in构造函数。
话虽如此,您仍然有所有权问题。没有人真正拥有你的 Arc
;对它的唯一外部引用是 weak_ptr
s.
(我花了几分钟才意识到我自己的评论就是答案……)
这里的问题是 shared_ptr
是在 Arc
之前(完全)构建的;如果异常中断 Arc
构造,则不应调用其析构函数,但销毁 skyhook
无论如何都会调用它。 (它 对 delete this
是合法的,甚至是间接合法的,但不是在这种情况下!)
因为是 impossible to release a shared_ptr
without trickery, the simplest thing to do is to provide a factory function (which avoids ):
struct Arc {
Node *left,*right;
private:
std::shared_ptr<Arc> skyhook; // will own *this
Arc(Node *l,Node *r) : left(l),right(r) {}
public:
static auto make(Node*,Node*);
void free() {skyhook.reset();}
};
auto Arc::make(Node *l,Node *r) {
const auto ret=std::make_shared<Arc>(l,r);
ret->left->arcs.push_back(ret);
ret->right->arcs.push_back(ret);
ret->skyhook=ret; // after securing Node references
return ret;
}
因为构造一个shared_ptr
必须分配,如果你关心bad_alloc
,这已经是必要的了。
背景
我有一个类似设置节点和边的网络。节点和边都需要 类,在本例中为 Node
或 Arc
、
问题
当构造函数抛出异常时,Visual Studio 和g++ with MinGW on Windows 无法捕捉到它,但没有错误处理退出(g++/MinGW 报告SIGTRAP 信号),而g++ and clang++ on Linux 正确处理异常。如果 Arc 无一例外地创建 Arc(n1, n2, false)
,所有编译器都可以正常工作。在所有情况下,都没有相关的编译器警告(使用 /W4 resp.-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 结束
这里似乎使用了 std::shared_ptr
以避免考虑生命周期和所有权,这会导致代码不佳。
更好的设计是有一个 class,比如 Network
,它拥有 Node
和 Arc
并将它们存储在 std::list
中。这样您就不需要 std::shared_ptr
或 std::week_ptr
以及使用它们产生的复杂代码。 Node
s 和 Arc
s 只能使用指向彼此的普通指针。
示例:
#include <list>
#include <vector>
#include <cstdio>
struct Node;
struct Arc {
Node *left, *right;
};
struct Node {
std::vector<Arc*> arcs;
};
class Network {
std::list<Node> nodes;
std::list<Arc> arcs;
public:
Node* createNode() {
return &*nodes.emplace(nodes.end());
}
Arc* createArc(Node* left, Node* right) {
Arc* arc = &*arcs.emplace(arcs.end(), Arc{left, right});
left->arcs.push_back(arc);
right->arcs.push_back(arc);
return arc;
}
};
int main() {
Network network;
Node* a = network.createNode();
Node* b = network.createNode();
Arc* ab = network.createArc(a, b);
std::printf("%p %p %p\n", a, b, ab);
}
tl;dr: 继承自 std::enable_shared_from_this
并使用 weak_from_this()
.
考虑以下结构,它与您的 (https://godbolt.org/z/vHh3ME) 相似:
struct thing
{
std::shared_ptr<thing> self{this};
thing()
{
throw std::exception();
}
};
在抛出异常时对象 *this
和 self
的状态是什么,哪些析构函数将作为堆栈展开的一部分执行?对象本身还没有完成构造,因此 ~thing()
不会(也不能)被执行。另一方面,self
是 完全构造的(成员在进入构造函数体之前被初始化)。因此,~std::shared_ptr<thing>()
将 执行,这将在未完全构造的对象上调用 ~thing()
。
从 std::enable_shared_from_this
继承不会出现此问题,假设在构造函数完成执行 and/or throws 之前没有创建实际的 shared_ptr
(weak_from_this()
将是您的朋友) ), 因为它只包含一个 std::weak_ptr
(https://godbolt.org/z/TGiw2Z); neither does a variant where your shared_ptr
is initialized at the end of the constructor (https://godbolt.org/z/0MkwUa),但是在你的案例中合并这并不容易,因为你给出了 shared/weak 指针 in构造函数。
话虽如此,您仍然有所有权问题。没有人真正拥有你的 Arc
;对它的唯一外部引用是 weak_ptr
s.
(我花了几分钟才意识到我自己的评论就是答案……)
这里的问题是 shared_ptr
是在 Arc
之前(完全)构建的;如果异常中断 Arc
构造,则不应调用其析构函数,但销毁 skyhook
无论如何都会调用它。 (它 对 delete this
是合法的,甚至是间接合法的,但不是在这种情况下!)
因为是 impossible to release a shared_ptr
without trickery, the simplest thing to do is to provide a factory function (which avoids
struct Arc {
Node *left,*right;
private:
std::shared_ptr<Arc> skyhook; // will own *this
Arc(Node *l,Node *r) : left(l),right(r) {}
public:
static auto make(Node*,Node*);
void free() {skyhook.reset();}
};
auto Arc::make(Node *l,Node *r) {
const auto ret=std::make_shared<Arc>(l,r);
ret->left->arcs.push_back(ret);
ret->right->arcs.push_back(ret);
ret->skyhook=ret; // after securing Node references
return ret;
}
因为构造一个shared_ptr
必须分配,如果你关心bad_alloc
,这已经是必要的了。