Как можно безопасно использовать `std :: error_category` в статических деструкторах? - PullRequest
0 голосов
/ 01 марта 2019

Рассмотрим пользовательский тип ошибки, написанный с использованием реализации LLVM system_category для справки:

#include <iostream>
#include <system_error>

struct my_error_category_type : std::error_category {
    char const* name() const  noexcept override { return "name"; }
    std::string message(int i) const noexcept override{ return "message"; }
    ~my_error_category_type() {
        std::cout << "Destroyed the category" << std::endl;
    }
};

std::error_category const& my_error_category() noexcept {
    static my_error_category_type c;
    return c;
}

Теперь представьте следующий простой класс, который использует std::error_code для обработки ошибок:

std::error_code do_some_setup() {
    return std::error_code(1, my_error_category());
}
std::error_code do_some_cleanup() {
    return std::error_code(2, my_error_category());
}

struct MyObj {
    void method() {
        // this constructs the category for the first time
        auto err = do_some_setup();
        std::cout << err << std::endl;
    }

    ~MyObj() {
        std::cout << "Running cleanup" << std::endl;
        auto err = do_some_cleanup();
        std::cout << err << std::endl;
    }
};

Следующий код дает тревожный вывод

static MyObj obj;

int main() {
    obj.method();  // remove this line, and the output is fine
}
name:1
Destroyed the category
Running cleanup
name:2

Обратите внимание, как my_error_category_type::message был вызван для разрушенного объекта!

Мои вопросы:

  1. Безопасен ли вызов message для этого разрушенного объекта?
  2. Если нет, есть ли способ сохранить время жизни категории?Можно ли как-нибудь сделать объект бессмертным?
  3. Стандарт дает какие-либо гарантии относительно срока службы встроенных std::system_category() объектов и тому подобного?Реализация LLVM, на которую я ссылаюсь выше, страдает точно такой же проблемой.

Ответы [ 2 ]

0 голосов
/ 01 марта 2019

Это похоже на ошибку в реализации std::system_category.

В качестве обходного пути, конструктор MyObj может вызвать std::system_category / my_error_category, так что категория статической ошибки функциипостроено до того, как MyObj построено и, следовательно, уничтожено только после уничтожения MyObj:

MyObj::MyObj() noexcept { static_cast<void>(my_error_category()); }
0 голосов
/ 01 марта 2019

После вызова деструктора небезопасно вызывать методы объекта.

Вопрос в том, как контролировать порядок уничтожения объектов.Статика уничтожается в обратном порядке инициализации, поэтому my_error_category_type будет уничтожен до MyObj, потому что его конструктор вызывается после конструктора MyObj.Это не проблема, которую нужно решать стандартно, а скорее архитектурная проблема.

Итак, мы должны каким-то образом контролировать порядок уничтожения.Самый простой способ - убедиться, что деструктор obj вызывается раньше:

void F() {
  MyObj obj;
  obj.method(); 
}

int main() {
  F();
}

Вывод программы:

name:1
Running cleanup
name:2
Destroyed the category

Теперь деструктор MyObj вызывается раньше, не после main, нопосле F() end, потому что MyObj - это переменная области видимости, и она уничтожается после F() finish и static my_error_category_type c при завершении main.

Но если мы все еще хотим сделать MyObj статическим, естьтакая техника называется Nifty Counter Idiom , которая помогает разрушать статику только после последнего использования.Но у него есть свои компромиссы. ссылка

Аналогичная проблема со статикой: «фиаско статического порядка инициализации» ( ссылка ).

...