在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构造函数。代码会编译,并且只构造/破坏一次,这就是我想要的-但是它可移植吗?
所以,问题:
或者我在这里还缺少其他细微之处?
答案 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 */);
}