【问题标题】:Catching exceptions from a constructor means that my instance is out of scope afterward从构造函数捕获异常意味着我的实例随后超出范围
【发布时间】:2014-08-25 21:17:57
【问题描述】:

我有一个类,它的构造函数可能会抛出异常。下面是一些将捕获异常的代码:

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块中调用这个函数。
  • 这没有意义。如果有异常,就没有对象,那么instance 有什么可能的需求?
  • "处理这种事情的惯用方法是什么?" - 根本不要使用try/catch,除非您可以在本地处理故障。
  • 考虑重新设计你的类,这样构造函数就不会再遇到导致他首先抛出MyClassException的错误情况。
  • 您打算如何处理“创建失败”对象?如果您需要存储创建失败的原因,请将其存储在异常中。

标签: c++ constructor exception-handling scope try-catch


【解决方案1】:

您应该在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 块是否真的在处理异常。如果你不能做任何事情来解决这种情况,你应该让它传播。

【讨论】:

  • 如果使用instance的代码也可以抛出异常怎么办?我会为这些添加额外的catch 块吗?我的直觉是在try 中放尽可能少的代码。
  • 但是如果使用实例也可以抛出许多异常,那么对来自代码不同部分的所有异常都使用一个 try-catch 是一种很好的方式吗?如果每组代码中的每组异常都有try-catch会更好看,不是吗?
  • @bdesham 不管怎样,如果你有一个try 块,它需要包含所有依赖于不抛出异常的东西。这甚至可能意味着它包含函数的全部内容(在这种情况下,您可以使用function try block)。
  • @JosephMansfield,可以,但是当人们不知道它是什么时,您的代码将无法通过审核:p
  • @bdesham:对于使用instance 的代码,您有两种选择。如果他们抛出,其他代码是否可以继续使用instance,或者在try块中创建的其他对象?如果是这样,那么你在这个 try 块中嵌套另一个 try/catch 块(或者更好的是,将它分解到一个带有 try/catch 块的函数中)。如果在其他操作抛出后您无法instance 执行任何操作,则要么向外部 try 块添加更多捕获器,要么让异常通过。
【解决方案2】:

您可以使用捕获异常的通用辅助函数和未来的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&lt;MyClass&gt;。测试结果并将实例的可用性与错误情况分开也很简单:

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 中添加一些日志记录。

【讨论】:

  • @LightnessRacesinOrbit 我无法判断 OP 用例的有效性。 如果它是有效的,答案可能会有所帮助。其他人已经指出,OP 应该首先检查这是否真的需要。
  • 最后一个代码块有点混乱; OP 的目标是 instance 在函数的其余部分的范围内,但在该代码中,它的范围是 if...else 块。建议不要吃最后一点。
  • @M.M 我认为这不是目标。目标是使用实例,但 在 try/catch 块之外,因此您不会意外地从该代码中捕获异常,而只是从 ctor 中捕获异常。这可以通过我的解决方案来实现(如代码中的注释所示)。
【解决方案3】:

使用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...

【讨论】:

  • 请注意,您可以在catch 之后写MyClass&amp; inst = *instance;,以便将来访问对象时不需要取消引用运算符
  • 在我的回答中使用optional 可以节省堆分配,尽管这没什么大不了的。
【解决方案4】:

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...

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-11-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多