Избегайте SIGTRAP, когда конструктор сети отбрасывает - PullRequest
1 голос
/ 25 июня 2019

Фон

У меня есть сетевая настройка с узлами и ребрами. И узлы, и ребра должны быть классами, в этом случае Node или Arc, , как в этом вопросе . В моей реальной работе я имею дело с множеством подклассов как Node, так и Arc. Для управления памятью я использую этот ответ на вопрос выше .

Задача

Когда конструктор выдает исключение, Visual Studio и g ++ с MinGW в Windows не могут его перехватить, но завершают работу без обработки ошибок (g ++ / MinGW сообщает о сигнале SIGTRAP), в то время как g ++ и clang ++ в Linux обрабатывают исключение правильно. Если 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()

С g ++ (7.4.0 и 8.3.0) или clang ++ (6.0.0) в Linux ...

работает как положено:

  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 ]

4 голосов
/ 25 июня 2019

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 не создает этой проблемы при условии, что фактические shared_ptr s не созданы до того, как конструктор завершит выполнение и / или броски (weak_from_this() будет вашим другом здесь), так как он содержит только std::weak_ptr (https://godbolt.org/z/TGiw2Z); и вариант, где ваш shared_ptr инициализируется в конце конструктора (https://godbolt.org/z/0MkwUa),, но это не тривиально для включения в ваш случай, так как вы даете общие / слабые указатели) в конструктор.

Как говорится, у вас все еще есть проблема собственности. Никто на самом деле не владеет вашим Arc; единственные внешние ссылки на это weak_ptr s.

2 голосов
/ 25 июня 2019

(Мне потребовалось несколько минут, чтобы понять, что мои собственные комментарии были ответом…)

Проблема здесь в том, что shared_ptr (полностью) построен до Arc является;если исключение прерывает конструкцию Arc, его деструктор не должен вызываться, но уничтожение skyhook вызывает его в любом случае.(Это является законным для delete this, даже косвенно, но не в этом контексте!)

Поскольку невозможно выпустить shared_ptr без обмана,самое простое, что нужно сделать, это предоставить фабричную функцию (которая позволяет избежать некоторых других проблем ):

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

Поскольку , создающий a shared_ptr, должен выделить, этоуже необходимо, если вас вообще беспокоит bad_alloc.

2 голосов
/ 25 июня 2019

Похоже, std::shared_ptr используется здесь, чтобы не думать о продолжительности жизни и владении, что приводит к плохому коду.

Лучший дизайн - иметь класс, скажем, Network, который владеет Node s и Arc s и сохраняет их в 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);
}
...