【问题标题】:Avoid SIGTRAP when the constructor of a network edge throws当网络边缘的构造函数抛出时避免 SIGTRAP
【发布时间】:2019-06-25 09:02:40
【问题描述】:

背景

我有一个像设置节点和边的网络。节点和边都需要是类,在本例中为 NodeArcas 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


【解决方案1】:

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&lt;thing&gt;()执行,它将在未完全构造的对象上调用~thing()

std::enable_shared_from_this 继承不会出现此问题,假设在构造函数完成执行和/或抛出之前没有创建实际的shared_ptrs(weak_from_this() 将是你的朋友),因为它只包含一个std::weak_ptr (https://godbolt.org/z/TGiw2Z);在构造函数(https://godbolt.org/z/0MkwUa)的末尾初始化您的shared_ptr 的变体也没有,但是在您的情况下合并这并不是一件容易的事,因为您正在提供共享/弱指针 in构造函数。

话虽如此,您仍然有所有权问题。没有人真正拥有您的Arc;唯一的外部引用是weak_ptrs。

【讨论】:

    【解决方案2】:

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

    这里的问题是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,这已经是必要的了。

    【讨论】:

      【解决方案3】:

      这里使用std::shared_ptr 似乎是为了避免考虑生命周期和所有权,这会导致代码质量不佳。

      更好的设计是有一个类,比如Network,它拥有Nodes 和Arcs 并将它们存储在std::list 中。这样你就不需要std::shared_ptrstd::week_ptr 以及使用它们产生的复杂代码。 Nodes 和 Arcs 可以相互使用纯指针。

      例子:

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

      【讨论】:

      • 感谢您的回答。问题是,所有权在我的真实代码中很复杂:代码是一个 C++ 库,主要用作使用 SWIG 包装的 CPython 扩展。用户是 Python 编写科学家,他们不应该为所有权而烦恼。我必须在 C++ 代码中解决这个问题。这样做会改变现有大型框架的整个 API。如果没有其他选择出现,我需要走这条路。
      • @oekopez 听起来你想要一个垃圾收集器,而不是 std::shared_ptr
      猜你喜欢
      • 2013-05-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-04-30
      相关资源
      最近更新 更多