2016-08-02 114 views
8

我有一个C++类,在构造函数失败时抛出一个异常。如何分配本类的本地实例(不使用new)并处理任何可能的异常,同时保持try块范围尽可能小?如何捕获构造函数异常?

从本质上讲,我找了C++相当于下面的Java成语:

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

我不想从x.doSomething()捕获异常,仅构造

我想我正在寻找的是一种方法来分开声明和x的初始化。

是否有可能在不使用堆分配和指针的情况下完成此操作?

+0

是的。将所有内容放在try块内的成功路径上。你会留在功能范围内。 – StoryTeller

+0

@StoryTeller如果我不想从例如'x.doSomething()',只有构造函数的异常? –

+0

@AndrewSun使用不同的异常,或将'x.doSomething()'放在内部异常块中。 – Holt

回答

11

可以使用std::optional从C++ 17:

bool foo() { 
    std::optional<Bar> x; // x contains nothing; no Bar constructed 
    try { 
     x.emplace();  // construct Bar (via default constructor) 
    } catch (const Exception& e) { 
     return false; 
    } 
    x->doSomething();  // call Bar::doSomething() via x (also (*x).doSomething()) 
    return true; 
} 
+0

@ Jarod42我对于如何将它写入'Bar'的Defualt ctor犹豫不决。 'x.emplace()'? 'x.emplace({})'? – songyuanyao

+1

默认构造函数是'x.emplace()'。后者不会编译。 – Jarod42

+0

@ Jarod42谢谢! – songyuanyao

2

你的

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = std::make_unique<Bar>(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (const BarConstructorException& e) { 
     return false; 
    } 
    return true; 
} 
2

变种之间进行选择一般来说,如果你想避免堆分配,你不能从它的定义局部变量的声明分开。所以,如果你是一切都在一个单一的功能结合起来,你就必须做环绕的x整个范围与try/catch块:

boolean foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 
5

是的,它是可能的,如果你把所有的代码try子句中,为例如,通过使用function try block(以避免不必要的嵌套和范围):

bool foo() try 
{ 
    Bar x; 
    x.doSomething(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

或在try子句调用另一个函数,它不会真正的工作:

void real_foo() 
{ 
    Bar x; 
    x.doSomething(); 
} 

bool foo() try 
{ 
    real_foo(); 
    return true; 
} 
catch (std::exception const& e) 
{ 
    return false; 
} 

请注意,在构造函数中抛出异常通常不是一个好主意,因为这会停止构造对象,并且不会调用其析构函数。


正如霍尔特指出,这也将赶上从doSomething通话例外。有解决了两种方式:

  1. 简单而标准的方法:使用指针

  2. 使用两阶段构造:有一个默认的构造函数不能抛出异常,然后调用一个特殊的“构造”函数来引发异常。

第二种方式是共同的前C++是标准化的,并为Symbian system在代码广泛使用。这种情况不常见,因为使用指针更容易,更简单,特别是在今天有很好的智能指针可用的情况下。我真的不推荐现代C++中的第二种方法。

最简单的方法当然是确保构造函数不能抛出异常,或者抛出任何异常,那么它们就是程序无法继续并且终止程序的本质。正如对你的问题的评论所指出的那样,C++中的异常是很昂贵的,然后我们也有被遗弃的构造问题,并且所有在C++中使用异常都只能在特殊情况下完成。 C++不是Java,即使在这两种语言中都有类似的结构,你也不应该这样对待它。

如果您仍想从构造函数中抛出异常,实际上只有第三种方法来捕获这些异常:使用顶部的代码示例之一,并只抛出doSomething永远不会抛出的特定异常,然后捕获这些特定只有构造函数。

+0

函数尝试块是不需要的。定期'尝试抓住'就够了。 – Jarod42

+0

@ Jarod42,那么'Bar'必须在另一个块中,而不是在功能级别块中。 – Ajay

+1

这不会模仿给定java代码的行为,因为您将从'x.doSomething()'中捕获异常。 – Holt

2

号从Java例子,你将有这两个可能性之间进行选择:

没有指针:

bool foo() { 
    try { 
     Bar x; 
     x.doSomething(); 
    } catch (Exception e) { 
     return false; 
    } 
    return true; 
} 

随着指针:

bool foo() { 
    Bar* x = nullptr; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 

    delete x; // don't forget to free memory 

    return true; 
} 

或者使用管理指针:

#include <memory> 

bool foo() { 
    std::unique_ptr<Bar> x; 
    try { 
     x = new Bar();    // until C++14 
     x = std::make_unique<Bar>(); // since C++14 
    } catch (Exception e) { 
     return false; 
    } 
    x->doSomething(); 
    return true; 
} 
+3

你可能想在某处添加一个'delete x'; – CompuChip

+0

通常,我使用托管指针。我的坏^^ – wasthishelpful

+1

是的。现在第二个例子的缺点是,你真的想要捕捉'x-> doSomething'中的任何异常,以确保'delete'被执行,但是你需要一个单独的try-catch块来重新抛出 - unique_ptr能很好地解决潜在的问题。很好,你添加了它。 – CompuChip

5

这个Java成语不会很好地转化为C++,因为Bar x;将需要默认构造函数即使您的真实构造函数需要传递参数。

我建议战斗语言这种程度 - 扩大try块就足够了 - 但是如果你真的缩小,那么你可以使用的功能,依靠返回值优化消除于未然值副本:

Bar foobar() 
{ 
    try { 
     return Bar(); 
    } catch (Exception& e){ 
     /* Do something here like throwing a specific construction exception 
      which you intercept at the call site.*/ 
    } 
} 

但是,真的,你可以抛出一个特定的异常构造,所以完全避免这个函数的方法。

+1

RVO和移动语义使我认为这是最好的解决方案。 – StoryTeller

+0

在C++ 17之前,OP可以处理复制/移动构造函数异常。 – Jarod42

+0

@ Jarod42,C++ 17改变了什么? (只是为了我自己的好奇而问)。 – StoryTeller

1

在修订讨论的OP补充说,

我不想从x.doSomething()捕获异常的要求,只有[局部变量]的构造函数。

一个简单的方法来翻译的Java代码

boolean foo() { 
    Bar x; 
    try { 
     x = new Bar(); 
    } catch (Exception e) { 
     return false; 
    } 
    x.doSomething(); 
    return true; 
} 

&hellip;到C++,然后使用一个Optional_类(如巴顿Nackmann Fallibleboost::optional或C++ 17 std::optional

auto foo() 
    -> bool 
{ 
    Optional_<Bar> xo; 
    try 
    { 
     xo.emplace(); 
    } 
    catch(...) 
    { 
     return false; 
    } 

    Bar& x = *xo; 
    // Possibly other code here, then: 
    x.doSomething(); 
    return true; 
} 

一个很好的替代方案是重构该代码,如下所示:

struct Something_failure {}; 

void do_something(Bar& o) 
{ 
    // Possibly other code here, then: 
    o.doSomething(); 
} 

auto foo() 
    -> bool 
{ 
    try 
    { 
     Bar x; 

     do_something(x); 
     return true; 
    } 
    catch(Something_failure const&) 
    { 
     throw; 
    } 
    catch(...) 
    {} 
    return false; 
} 

如果你不喜欢上述方法,那么你总是可以去动态分配的Bar实例,例如使用std::unique_ptr进行有保证的清理,但是这具有动态分配的一般开销。在Java中,大多数每个对象都是动态分配的,所以看起来似乎不是一个严重的缺点。但是在C++中,大多数对象是超快堆栈分配的,所以与普通操作相比,动态分配是一个非常慢的操作,所以必须对动态分配的概念简单性进行加权。