Подтвердить что ты не робот

Существует ли правило исключения встраивания std::string в исключениях с помощью конструкторов перемещения?

Я слышал некоторое время назад, что я не должен создавать классы исключений, у которых были бы поля типа std::string. Об этом говорит сайт Boost. Обоснование заключается в том, что конструктор std::string copy может генерировать исключение, если распределение памяти не выполняется, и если исключение вызывается до того, как пойманное обработанное исключение будет поймано, программа прекращается.

Однако, он все еще держится в мире конструкторов перемещения? Не будет ли использоваться конструктор перемещения вместо конструктора копирования при выбросе исключения? Правильно ли я понимаю, что с С++ 11 не будет выделено выделение памяти, нет никаких шансов на исключение, а std::string теперь абсолютно нормально в классах классов?

4b9b3361

Ответ 1

Ответ:

Да, вы все равно не хотите вставлять std::string в свои типы исключений. Исключения часто копируются, иногда без вашего ведома. Например, на некоторых платформах std::rethrow_exception будет копировать исключение (а на некоторых - нет).

Для лучшей практики сохраните свой конструктор копирования noexcept.

Однако все не потеряно. Малоизвестный факт заключается в том, что в С++ всегда существовал стандарт стандартного неизменяемого типа строк с подсчетом (с конструктором без метаданных), просто с запутанным именем. На самом деле два имени:

logic_error
runtime_error

Спецификации для этих типов таковы, что они должны содержать неизменяемый объект, подобный объекту с подсчетом. Ну, не совсем непреложный. Вы можете заменить строку назначением. Но вы не можете изменить строку на месте.

Мой совет заключается в том, чтобы либо получить один из этих типов, либо, если это неприемлемо, вставить один из этих типов и рассматривать его как неизменяемый тип строки, подсчитанный счетчиком:

#include <stdexcept>
#include <iostream>

class error1
    : public std::runtime_error
{
    using msg_ = std::runtime_error;
public:
    explicit error1(std::string const& msg)
        : msg_(msg)
    {}
};

class error2
{
    std::runtime_error msg_;
public:
    explicit error2(std::string const& msg)
        : msg_(msg)
    {}

    char const* what() const noexcept {return msg_.what();}
};

void
test_error1()
{
    try
    {
        throw error1("test1");
    }
    catch (error1 const& e)
    {
        std::cout << e.what() << '\n';
    }
}

void
test_error2()
{
    try
    {
        throw error2("test2");
    }
    catch (error2 const& e)
    {
        std::cout << e.what() << '\n';
    }
}

int
main()
{
    test_error1();
    test_error2();
}

std:: lib позаботится обо всем управлении строкой и управлении памятью для вас, и вы получите noexcept копирование в сделке:

static_assert(std::is_nothrow_copy_constructible<error1>{}, "");
static_assert(std::is_nothrow_copy_assignable   <error1>{}, "");
static_assert(std::is_nothrow_copy_constructible<error2>{}, "");
static_assert(std::is_nothrow_copy_assignable   <error2>{}, "");

Ответ 2

Выполняется ли копия string, когда генерируется исключение, зависит от блока catch.

Возьмем этот пример:

#include <iostream>

struct A
{
};

void foo()
{
   throw A();
}

void test1()
{
   try
   {
      foo();
   }
   catch (A&& a)
   {
   }
}

void test2()
{
   try
   {
      foo();
   }
   catch (A const& a)
   {
   }
}

void test3()
{
   try
   {
      foo();
   }
   catch (A a)
   {
   }
}

int main()
{
   test1();
   test2();
   test3();
}

Вы не сделаете копию A в test1 или test2, но в итоге вы сделаете копию в test3.