从函数返回RAII对象

时间:2020-02-24 19:07:32

标签: c++ c++14 c++17

在C ++ 14中,我试图定义一个“ RAII factory”函数,如下所示:

// Here, "ScopedReseource" is a plain RAII class, managing some resource.

ScopedResource factory(/* args */) {
    return ScopedReseource(/* args */);
}

客户端使用情况为:

{
    auto scoped = factory(/* args */));
    // use scoped...
}

据我了解,C ++ 14语言无法保证复制省略,因此这是不可靠的。 特别是,它可能导致ScopedResource的析构函数在factory()函数的末尾被调用(并在调用站点上创建一个新副本)。我不要。

传统的客户端代码当然可以正常工作:

{
    ScopedResource scoped(/* args */);
    // use scoped...
}

现在,我尝试删除ScopedResource的副本构造函数(=delete),并定义一个move构造函数。代码会编译,并且只构造/破坏一次,这就是我想要的-但是它可移植吗?

所以,问题:

  • 我是否正确,在C ++ 17之前返回RAII对象(析构函数执行我想做的特殊工作一次)是不可行的?
    • 我删除副本构造函数并定义移动构造函数的方法是否有效/可移植?
  • 我对吗,在C ++ 17中,您可以用上面的简单代码来移植吗?

或者我在这里还缺少其他细微之处?

2 个答案:

答案 0 :(得分:3)

据我了解,C ++ 14语言无法保证复制省略,因此这是不可靠的。特别是,这可能导致在factory()函数的末尾调用ScopedResource的析构函数(并在调用站点上创建一个新副本)。我不要。

为什么不呢?

如果您的类型是可移动的,则意味着从其移出的对象将包含“空”资源。也就是说,它当前不会与资源关联。因此,它的析构函数将无能为力,并且所管理的资源仍将存在。

因此,在C ++ 14中按值返回这样的对象没有问题。是的,析构函数可能会被调用,但是由于它什么都不做,谁在乎?

我是对的,在C ++ 17之前返回RAII对象(析构函数执行一次我想做的特殊工作)是不可行的吗?

不,您不正确。只要您将对象编码为表示没有资源的“空”状态(这是仅移动类型的标准状态),则“特殊工作”将仅由单个对象完成。可能会调用多个析构函数,但是只有一个这样的调用会在释放资源中发挥重要作用。

这是关于正确实现仅移动类型,而不是复制省略。

答案 1 :(得分:1)

我是对的,在C ++ 17之前返回RAII对象(析构函数执行特殊工作)是不可行的吗?

通常,不会。应该构建RAII类型以处理复制/移动,以便可以按值返回它。

在您的特定情况下,由于您的RAII类型不执行C ++ 17保证的复制省略,您是正确的。 (这意味着它实际上不是RAII类型)

我删除副本构造函数并定义移动构造函数的方法是否有效/可移植?

这是一种阻止复制对象的有效方法,但是在C ++ 17之前,该对象又可以移出函数,并且析构函数在函数中保留的存根上运行。如果您的目标是在调用站点中销毁对象之前不调用析构函数,那么这不是您的解决方案。

我对吗,在C ++ 17中,您可以使用上面的简单代码轻松地实现它吗?

是的。在C ++ 17及更高版本中

ScopedResource factory(/* args */) {
    return ScopedReseource(/* args */);
}

auto scoped = factory(/* args */));

归结为

auto scoped = ScopedReseource(/* args */);

如果不能保证将拥有C ++ 17,则可以使用std::unique_ptr来封装对象。这样可以确保即使不应用RVO / NRVO,也不会破坏对象。那会给你

std::unique_ptr<ScopedResource> factory(/* args */) {
    return make_unique<ScopedReseource>(/* args */);
}