构造函数中的 RAII 和异常

RAII and exception in constructor

本文关键字:异常 RAII 构造函数      更新时间:2023-10-16

想象一下,我有一份工作要做,可以通过三种不同的方式完成:一种缓慢而痛苦但万无一失的方式;一种是适度痛苦的方式,因为你有Resource1;以及一种快速简便的方法,既需要Resource1,也需要Resource2。现在,这些资源很宝贵,所以我将它们包装成 RAII 实现ResNHolder并编写如下内容:

void DoTheJob(Logger& log/*, some other params */) {
    try {
        Res1Holder r1(/* arguments for creating resource #1 */);
        try {
            Res2Holder r2(/* arguments */);
            DoTheJobQuicklyAndEasily(log, r1, r2);
        }
        catch (Res2InitializationException& e) {
            log.log("Can't obtain resource 2, that'll slowdown us a bit");
            DoTheJobWithModerateSuffering(log, r1);
        }
    }
    catch (Res1InitializationException& e) {
        log.log("Can't obtain resource 1, using fallback");
        DoTheJobTheSlowAndPainfulWay(log);
    }
}

"DoTheJobXxx()" 引用 Logger/ResNHolder ,因为它们是不可复制的。我是不是做得太笨拙了?有没有其他聪明的方法来构造函数?

我认为您的代码会很好,但这里有一个可以考虑的替代方案:

void DoTheJob(Logger &log/*,args*/)
{
    std::unique_ptr<Res1Holder> r1 = acquireRes1(/*args*/);
    if (!r1) {
        log.log("Can't acquire resource 1, using fallback");
        DoTheJobTheSlowAndPainfulWay(log);
        return;
    }
    std::unique_ptr<Res2Holder> r2 = acquireRes2(/*args*/);
    if (!r2) {
        log.log("Can't acquire resource 2, that'll slow us down a bit.");
        DoTheJobWithModerateSuffering(log,*r1);
        return;
    }
    DoTheJobQuicklyAndEasily(log,*r1,*r2);
}

其中,当资源初始化失败时,acquireRes 函数返回空unique_ptr:

std::unique_ptr<Res1Holder> acquireRes1()
{
  try {
    return std::unique_ptr<Res1Holder>(new Res1Holder());
  }
  catch (Res1InitializationException& e) {
    return std::unique_ptr<Res1Holder>();
  }
}
std::unique_ptr<Res2Holder> acquireRes2()
{
  try {
    return std::unique_ptr<Res2Holder>(new Res2Holder());
  }
  catch (Res2InitializationException& e) {
    return std::unique_ptr<Res2Holder>();
  }
}

你的代码看起来不错,这是我能想象到的唯一问题,你可能在性能方面遇到,因为例外被认为是不是很有效。如果是这样,您可以将代码更改为:

void DoTheJob(Logger& log/*, some other params */) {
    Res1HolderNoThrow r1(/* arguments for creating resource #1 */);
    if( r1 ) {
        Res2HolderNoThrow r2(/* arguments */);
        if( r2 ) 
            DoTheJobQuicklyAndEasily(log, r1, r2);
        else {
            log.log("Can't obtain resource 2, that'll slowdown us a bit");
            DoTheJobWithModerateSuffering(log, r1);
        }
    } else {
        log.log("Can't obtain resource 1, using fallback");
        DoTheJobTheSlowAndPainfulWay(log);
    }
}

您将需要另一个 RAII 对象,该对象不会引发异常但具有状态并在运算符 bool() 或其他位置返回它。但是你的代码对我来说看起来不太容易出错,我宁愿使用它,除非你有性能问题或需要避免异常。