当网络边缘的构造函数抛出异常时,请避免使用SIGTRAP

时间:2019-06-25 09:02:40

标签: c++ exception visual-c++ g++ mingw

背景

我有一个像设置节点和边的网络。节点和边都需要是类,在这种情况下为NodeArcas in this question。在我的实际设置中,我正在处理Node和Arc的许多子类。对于内存管理,我使用this answer to the question above

问题

当构造函数引发异常时,Windows上带有MinGW的Visual Studio和g ++无法捕获它,但是在没有错误处理的情况下退出(g ++ / MinGW报告SIGTRAP信号),而Linux上的g ++和clang ++可以正确处理该异常。如果在没有异常Arc(n1, n2, false)的情况下创建Arc,则所有编译器都可以正常工作。在所有情况下,都没有相关的编译器警告(使用/ 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结尾

3 个答案:

答案 0 :(得分:4)

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();
  }
};

在引发异常时,对象*thisself的状态是什么,以及哪些析构函数将在堆栈展开时执行?对象本身尚未完成构造,因此~thing()将不会(并且一定不能)执行。另一方面,self 完全构造的(在输入构造函数主体之前初始化成员)。因此,~std::shared_ptr<thing>() 执行,这将在未完全构造的对象上调用~thing()

std::enable_shared_from_this继承不会出现此问题,假设在构造函数完成执行和/或抛出之前没有创建任何实际的shared_ptrweak_from_this()是您的朋友),因为它仅持有std::weak_ptrhttps://godbolt.org/z/TGiw2Z);在构造函数(https://godbolt.org/z/0MkwUa)的末尾初始化shared_ptr的变体也没有,但是在您的案例中并没有那么简单,因为您要在中提供共享/弱指针构造函数。

话虽如此,您仍然有所有权问题。实际上没有人拥有您的Arc;唯一的外部引用是weak_ptr

答案 1 :(得分:2)

这里似乎使用std::shared_ptr来避免考虑生存期和所有权,这会导致代码不正确。

更好的设计是拥有一个类,例如Network,该类拥有NodeArc并将它们存储在std::list中。这样,您就不需要std::shared_ptrstd::week_ptr以及使用它们而产生的复杂代码。 NodeArc 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);
}

答案 2 :(得分:2)

(我花了几分钟才意识到自己的评论就是答案……)

这里的问题是shared_ptr是在Arc之前(完全)构造的;如果异常中断了Arc的构造,则不应调用其析构函数,但销毁skyhook仍将对其进行调用。 ( delete this是合法的,甚至是间接的,但在这种情况下不合法!)

由于impossible to release a shared_ptr毫无欺骗性,所以最简单的方法是提供工厂函数(避免使用certain other problems):

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,这已经是必需的。