Как правильно выбрасывать исключение, которое требует больше, чем просто конструктор?
У меня есть класс Exception, на котором я хочу установить дополнительную информацию, прежде чем я его выброшу. Могу ли я создать объект Exception, вызвать некоторые его функции, а затем выбросить его без каких-либо копий его создания?
Единственный метод, который я нашел, - это бросить указатель на объект:
class Exception : public std::runtime_error
{
public:
Exception(const std::string& msg) : std::runtime_error(msg) {}
void set_line(int line) {line_ = line;}
int get_line() const {return line_;}
private:
int line_ = 0;
};
std::unique_ptr<Exception> e(new Exception("message"));
e->set_line(__LINE__);
throw e;
...
catch (std::unique_ptr<Exception>& e) {...}
Но исключение исключений указателем обычно избегают, так есть ли другой способ?
Существует также возможность установки всех параметров через конструктор, но это может быстро стать нескромным, если к классу добавлено больше полей, и вы хотите иметь мелкомасштабный контроль над тем, какие поля установить:
throw Exception("message"); // or:
throw Exception("message", __LINE__); // or:
throw Exception("message", __FILE__); // or:
throw Exception("message", __LINE__, __FILE__); // etc.
Ответы
Ответ 1
Как насчет использования std:: move?
Exception e("message");
e.set_line(__LINE__);
throw std::move(e);
Кроме того, вы можете создать построитель Java-esque, например:
class ExceptionBuilder;
class Exception : public std::runtime_error
{
public:
static ExceptionBuilder create(const std::string &msg);
int get_line() const {return line_;}
const std::string& get_file() const { return file_; }
private:
// Constructor is private so that the builder must be used.
Exception(const std::string& msg) : std::runtime_error(msg) {}
int line_ = 0;
std::string file_;
// Give builder class access to the exception internals.
friend class ExceptionBuilder;
};
// Exception builder.
class ExceptionBuilder
{
public:
ExceptionBuilder& with_line(const int line) { e_.line_ = line; return *this; }
ExceptionBuilder& with_file(const std::string &file) { e_.file_ = file; return *this; }
Exception finalize() { return std::move(e_); }
private:
// Make constructors private so that ExceptionBuilder cannot be instantiated by the user.
ExceptionBuilder(const std::string& msg) : e_(msg) { }
ExceptionBuilder(const ExceptionBuilder &) = default;
ExceptionBuilder(ExceptionBuilder &&) = default;
// Exception class can create ExceptionBuilders.
friend class Exception;
Exception e_;
};
inline ExceptionBuilder Exception::create(const std::string &msg)
{
return ExceptionBuilder(msg);
}
Используется следующим образом:
throw Exception::create("TEST")
.with_line(__LINE__)
.with_file(__FILE__)
.finalize();
Ответ 2
Предполагается, что классы исключений С++ будут скопируемыми или, по крайней мере, подвижными. В вашем примере создание класса, пригодного для копирования, связано с добавлением конструктора копии по умолчанию:
Exception(Exception const&) = default;
Если вам нужно инкапсулировать какое-то не скопированное и непередвижное состояние в свой класс исключения, оберните это состояние в std:: shared_ptr.
Ответ 3
Вы можете создать класс хранения данных, например ExceptionData
. Затем создайте объект ExceptionData
и вызовите его. Затем создайте объект Exception
, используя std::move
в ctor, например:
ExceptionData data;
data.method();
throw Exception(std::move(data));
Конечно, ExceptionData
должен быть подвижным, и вам нужно иметь ctor, который принимает ExceptionData &&
(rvalue reference).
Это будет работать, если вам действительно нужно избегать копий, но для меня это похоже на предварительную оптимизацию. Подумайте, как часто в вашем приложении выбрасываются исключения, и действительно ли это стоит того, чтобы усложнять ситуацию.