如何正确抛出一个不仅仅需要构造函数的异常?
How to properly throw an exception which needs more than just a constructor?
我有一个异常 class,我想在抛出它之前设置更多信息。我可以创建 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) {...}
但是指针抛出异常一般是可以避免的,请问有没有其他办法呢?
还有通过构造函数设置所有选项的选项,但如果将更多字段添加到 class 并且您想对哪些字段进行细粒度控制,这很快就会变得不可扩展设置:
throw Exception("message"); // or:
throw Exception("message", __LINE__); // or:
throw Exception("message", __FILE__); // or:
throw Exception("message", __LINE__, __FILE__); // etc.
C++ 异常 classes 应该是可复制的或至少是可移动的。在您的示例中,使 class 可复制是添加默认复制构造函数的问题:
Exception(Exception const&) = default;
如果您需要在异常中封装一些不可复制和不可移动的状态class,请将此类状态包装到std::shared_ptr。
您可以创建一个数据持有 class,例如 ExceptionData
。然后创建 ExceptionData
对象并调用它的方法。然后在 ctor 中使用 std::move
创建 Exception
对象,例如:
ExceptionData data;
data.method();
throw Exception(std::move(data));
当然,ExceptionData
需要是可移动的,你必须有接受 ExceptionData &&
(右值引用)的 ctor。
如果你真的需要避免副本,它会起作用,但对我来说这感觉像是初步优化。想一想在您的应用程序中抛出异常的频率,是否真的值得将事情复杂化。
使用 std::move 怎么样?
Exception e("message");
e.set_line(__LINE__);
throw std::move(e);
或者,您可以像这样创建一个 Java 式的构建器:
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();
我有一个异常 class,我想在抛出它之前设置更多信息。我可以创建 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) {...}
但是指针抛出异常一般是可以避免的,请问有没有其他办法呢?
还有通过构造函数设置所有选项的选项,但如果将更多字段添加到 class 并且您想对哪些字段进行细粒度控制,这很快就会变得不可扩展设置:
throw Exception("message"); // or:
throw Exception("message", __LINE__); // or:
throw Exception("message", __FILE__); // or:
throw Exception("message", __LINE__, __FILE__); // etc.
C++ 异常 classes 应该是可复制的或至少是可移动的。在您的示例中,使 class 可复制是添加默认复制构造函数的问题:
Exception(Exception const&) = default;
如果您需要在异常中封装一些不可复制和不可移动的状态class,请将此类状态包装到std::shared_ptr。
您可以创建一个数据持有 class,例如 ExceptionData
。然后创建 ExceptionData
对象并调用它的方法。然后在 ctor 中使用 std::move
创建 Exception
对象,例如:
ExceptionData data;
data.method();
throw Exception(std::move(data));
当然,ExceptionData
需要是可移动的,你必须有接受 ExceptionData &&
(右值引用)的 ctor。
如果你真的需要避免副本,它会起作用,但对我来说这感觉像是初步优化。想一想在您的应用程序中抛出异常的频率,是否真的值得将事情复杂化。
使用 std::move 怎么样?
Exception e("message");
e.set_line(__LINE__);
throw std::move(e);
或者,您可以像这样创建一个 Java 式的构建器:
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();