我有一个类,其构造函数可能会抛出异常。这里有一些代码可以捕获异常:
try {
MyClass instance(3, 4, 5);
}
catch (MyClassException& ex) {
cerr << "There was an error creating the MyClass." << endl;
return 1;
}
但是当然,在try / catch之后没有代码可以看到instance
,因为它现在超出了范围。解决这个问题的一种方法是分别声明和定义instance
:
MyClass instance;
try {
MyClass instance(3, 4, 5);
}
...
除了我的类没有适当的零参数构造函数。实际上,这里的情况就是这样一个构造函数甚至有意义的唯一一个:MyClass
对象是不可变的,因为它的数据成员在构造之后都没有变化。如果我要添加一个零参数构造函数,我需要引入一些像is_initialized_
这样的实例变量,然后在继续之前检查每个方法以确保该变量是true
。对于这样一个简单的模式来说,这似乎太过冗长。
处理这种事情的惯用方法是什么?我是否需要将其填充并允许在初始化之前声明我的类的实例?
您应该在try
块中执行您需要执行的所有操作:
try {
MyClass instance(3, 4, 5);
// Use instance here
}
catch (MyClassException& ex) {
cerr << "There was an error creating the MyClass." << endl;
return 1;
}
毕竟,只有在try
区块内才能成功创建instance
,因此可以使用。
我想知道你的catch
块是否真的处理异常。如果你无法解决问题,你应该让它传播。
使用new
动态分配实例:
std::unique_ptr<MyClass> instance;
try
{
instance.reset(new MyClass(3, 4, 5));
}
catch (const MyClassException& ex)
{
std::cerr << "There was an error creating the MyClass." << std::endl;
return 1;
}
// use instance as needed...
您可以使用捕获异常的通用辅助函数和未来的std::optional
(或boost::optional
)来表示实例的成功或失败的创建:
template< typename T, typename... Args >
std::optional< T > try_make( Args&&... args )
{
try {
return T{ std::forward< Args >( args )... };
}
catch( ... ) {
return {};
}
}
基本上使用这个:
auto instance = try_make< MyClass >(3, 4, 5);
instance
现在是optional<MyClass>
。要测试结果并将实例与错误情况分开可用性也很简单:
if( auto instance = try_make< MyClass >( 3, 4, 5 ) ) {
// use *instance, but this code is *not* in the try/catch block!
}
else {
// creating the instance failed
}
当然,异常信息将以这种方式丢失,但您可以选择不太通用的函数,并根据需要在catch-block中添加一些日志记录。
Remy的答案的变体,但使用std::optional
保存动态分配:
std::optional<MyClass> instance_opt;
try {
// could use `instance = MyClass(3, 4, 5)`, but that requires you to write a move constructor
instance_opt.emplace(3, 4, 5);
}
catch (const MyClassException& ex) {
std::cerr << "There was an error creating the MyClass." << std::endl;
return 1;
}
MyClass& instance = *instance_opt;
// use instance as needed...