C++ 处理关键对象的构造函数失败的好风格

C++ 处理关键对象的构造函数失败的好风格,c++,exception,constructor,C++,Exception,Constructor,我试图在实例化一个对象的两种方法之间做出选择&处理对我的程序至关重要的对象的任何构造函数异常,也就是说,如果构造失败,程序将无法继续 我有一个SimpleMIDIOut类,它封装了基本的Win32 MIDI函数。它将在构造函数中打开MIDI设备,并在析构函数中关闭它。如果MIDI设备无法打开,它将抛出从构造函数中的std::exception继承的异常 下列方法中,捕获对象的构造函数异常的方法将更符合C++最佳实践 方法1-堆栈分配的对象,仅在try块内的范围内 #include <ios

我试图在实例化一个对象的两种方法之间做出选择&处理对我的程序至关重要的对象的任何构造函数异常,也就是说,如果构造失败,程序将无法继续

我有一个SimpleMIDIOut类,它封装了基本的Win32 MIDI函数。它将在构造函数中打开MIDI设备,并在析构函数中关闭它。如果MIDI设备无法打开,它将抛出从构造函数中的std::exception继承的异常

下列方法中,捕获对象的构造函数异常的方法将更符合C++最佳实践

方法1-堆栈分配的对象,仅在try块内的范围内

#include <iostream>
#include "simplemidiout.h"

int main()
{
    try
    {
        SimpleMIDIOut myOut;  //constructor will throw if MIDI device cannot be opened
        myOut.PlayNote(60,100);

        //.....
        //myOut goes out of scope outside this block
        //so basically the whole program has to be inside 
        //this block.
        //On the plus side, it's on the stack so 
        //destructor that handles object cleanup
        //is called automatically, more inline with RAII idiom?
    }
    catch(const std::exception& e)
    {
        std::cout << e.what() << std::endl;
        std::cin.ignore();
        return 1;
    }

    std::cin.ignore();
    return 0;   
}
#包括
#包括“simplemidiout.h”
int main()
{
尝试
{
SimpleMIDIOut myOut;//如果无法打开MIDI设备,构造函数将抛出
myOut.PlayNote(60100);
//.....
//myOut超出此块的范围
//所以基本上整个程序都必须在里面
//这个街区。
//好的一面是,它在堆栈上,所以
//处理对象清理的析构函数
//是自动调用的,更符合RAII的习惯用法?
}
捕获(const std::exception&e)
{

std::cout就我个人而言,我更喜欢您使用的第一种样式-方法1-将
SimpleMIDIOut
对象分配为try-catch块范围的本地对象

  • 对我来说,try-catch块的好处之一是它为错误处理代码提供了一个整洁的地方,catch块允许您在一个漂亮的、可读的、不间断的流中指定您的业务逻辑

  • 其次,您指定的try-catch块足够通用,可以处理从
    std::exception
    派生的任何异常-因此,将大部分程序代码放在try-catch块中并不是一件坏事。如果发生最坏的情况并引发异常,它将防止程序意外终止。Potentia这可能是一个您不期望的异常,比如索引越界或内存分配异常

  • 如果出于可读性的原因,您不喜欢在try-catch块中包含大量代码,那也没关系,因为这样做是很好的设计实践。通过这样做,您可以将主函数本身的行数保持在最少

查看方法2:

  • 您不需要catch块中的
    delete
    。如果在构造过程中抛出异常,那么那里就没有要删除的对象

  • 你有没有考虑过你打算如何处理任何可能被
    myOut->PlayNote
    抛出的
    std::exception
    ?它不在你的try-catch范围之内,所以这里的异常会意外地杀死程序。这就是我上面第二个子弹的意思

如果您决定将大部分程序包装在该try-catch块中,但仍希望动态分配
SimpleMIDIOut
对象,则可以通过使用在发生异常时为您管理内存来简化内存管理:

try
{
    std::auto_ptr<SimpleMIDIOut> myOut(new SimpleMIDIOut());
    myOut->PlayNote(60,100);
    std::cin.ignore();
} // myOut goes out of scope, SimpleMIDIOut object deleted
catch(const std::exception& e)
{
    std::cout << e.what() << std::endl;
    return 1;
}


return 0;
试试看
{
std::auto_ptr myOut(新的SimpleMIDIOut());
myOut->PlayNote(60100);
std::cin.ignore();
}//myOut超出范围,SimpleMIDIOut对象已删除
捕获(const std::exception&e)
{

std::cout您能否澄清您是否可以控制SimpleDiout中的源代码

如果执行此操作,则此类不应从CTOR引发异常。该类的CTOR中的代码应使用try\catch块包装


如果你不这样做,那么为了清楚起见,我会选择方法1——整个程序必须在这个try块中,这一事实可以通过重构成小方法来解决。

从构造函数中抛出一个异常是完全有效的,它可以向调用方指出,无论出于什么原因,构造都无法完成。这是有效的——但在我的例子中观点:糟糕的设计可能导致运行时间延长exceptions@drelihan另一种方法是允许一个对象在一个不可用的状态下构建,然后等待它在某个地方使用。一个更差的设计选择IMHO。@ SGReFE公平点-但是C++破坏了完全构建的对象,并且对象直到它的CO才被完全构造。构造函数已完成。这可能会导致未定义的行为-尤其是在分配了新内存等情况下。@drelihan也是一个非常公平的观点。对于一个编写良好的构造函数来说,如果它失败,它会清除分配的内存。对于一个问得很好的第一个问题+1。感谢您的详细回答。值得思考。将其全部嵌入到这个try/catch块在美学层面上对我来说似乎很奇怪,我想对代码结构进行一次理智的检查。这听起来可能很奇怪,但实际上将
main
中的代码封装到try/catch块中是一个非常好的做法,以防止意外终止(并记录异常以进行分析).我应该注意到,尽管这是一种绝望的措施,只适用于
main
和类似main的函数,但不要养成将每个函数体都包含在try/catch中的习惯;)
try
{
    std::auto_ptr<SimpleMIDIOut> myOut(new SimpleMIDIOut());
    myOut->PlayNote(60,100);
    std::cin.ignore();
} // myOut goes out of scope, SimpleMIDIOut object deleted
catch(const std::exception& e)
{
    std::cout << e.what() << std::endl;
    return 1;
}


return 0;