【问题标题】:RAII and exception in constructor构造函数中的 RAII 和异常
【发布时间】:2013-10-01 08:34:54
【问题描述】:

想象我有一份工作要做,可以通过三种不同的方式来完成:一种缓慢而痛苦但安全可靠的方式;中度痛苦的方式,假设你有Resource1;还有一种快速简便的方法,它需要Resource1Resource2。现在,这些资源很宝贵,所以我将它们包装到实现 RAII 的 ResNHolders 中并编写如下内容:

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,因为它们是不可复制的。是我做得太笨了吗?有没有其他巧妙的方法来构造函数?

【问题讨论】:

  • 我觉得没问题。
  • 这可以作为 try-catch 的教科书示例。
  • 我会使用返回可选 对象而不是异常的工厂方法。
  • 这里面的函数名棒极了

标签: c++ exception raii


【解决方案1】:

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

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 函数返回 null 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>();
  }
}

【讨论】:

  • +1 虽然问题中的原始代码是正确的,但这会减少 DoTheJob 函数中的缩进。这是一个品味问题,但我认为这更具可读性。
  • 为什么在已经是 RAII 的资源周围有一个 unique_ptr?
  • @DieterLücking:将其设为指针可以让资源在函数之间有效地传输,并且它自然有一个空值表示无法获取资源。使用 std::unique_ptr 而不是使用原始指针可确保资源自动释放。
  • @VaughnCato 我知道,但是资源已经实现了!
  • @DieterLücking:实现了什么?一个空状态?
【解决方案2】:

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

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 对象,它不会抛出异常但具有状态并在 operator bool() 或其他地方返回它。但是您的代码在我看来不太容易出错,除非您遇到性能问题或需要避免异常,否则我更愿意使用它。

【讨论】:

    猜你喜欢
    • 2013-01-20
    • 2014-07-06
    • 2012-02-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-01
    • 1970-01-01
    • 2023-04-07
    相关资源
    最近更新 更多