是否可以返回不可移动,不可复制类型的实例?

时间:2015-12-14 09:50:25

标签: c++ language-lawyer

在VS2013更新5中,我有这个:

class Lock
{
public:
    Lock(CriticalSection& cs) : cs_(cs)
    {}

    Lock(const Lock&) = delete;
    Lock(Lock&&) = delete;
    Lock& operator=(const Lock&) = delete;
    Lock& operator=(Lock&&) = delete;

    ~Lock()
    {
        LeaveCriticalSection(&(cs_.cs_));
    }

private:
    CriticalSection& cs_;
};


class CriticalSection
{
    CRITICAL_SECTION cs_;
public:
    CriticalSection(const CriticalSection&) = delete;
    CriticalSection& operator=(const CriticalSection&) = delete;
    CriticalSection(CriticalSection&&) = delete;
    CriticalSection& operator=(CriticalSection&&) = delete;

    CriticalSection()
    {
        InitializeCriticalSection(&cs_);
    }

    ~CriticalSection()
    {
        DeleteCriticalSection(&cs_);
    }

    // Usage:  auto lock = criticalSection.MakeLock();
    Lock MakeLock()
    {
        EnterCriticalSection(&cs_);
        return Lock(*this);
    }
}

MakeLock返回不可移动,不可复制类型的实例。这似乎工作正常。但是,Visual Studio intellisense确实强调了红色的返回,并警告说Lock的移动构造函数不能被引用,因为它是一个已删除的函数。

我试图理解为什么这有效,如果它是标准符合C ++或只是MSVC特有的东西。我猜回归是有效的,因为构建返回值的需要可以被优化掉,所以intellisense警告警告一些事实上没有 - 实际上 - 实际发生。

我想我在某处读到C ++会标准化以确保返回值优化总是会发生。

那么,这个符合C ++的代码是否会继续在未来的编译器中运行?

P.S。我意识到std::mutexstd::lock_guard可能会取代这个。

3 个答案:

答案 0 :(得分:8)

如果编译,则编译器中存在错误。 VC2015无法正确编译它。

class Foo
{
public:
    Foo() {}
    Foo(const Foo&) = delete;
    Foo(Foo&&) = delete;
};


Foo Bar()
{
    return Foo();
}

给我:

xxx.cpp(327): error C2280: 'Foo::Foo(Foo &&)': attempting to reference a deleted function

和g ++ 4.9说:

error : use of deleted function 'Foo::Foo(Foo&&)'

标准非常清楚,复制构造函数或移动构造函数必须存在且可访问,即使RVO意味着它未被调用。

答案 1 :(得分:5)

在C ++ 17中,Martin Bonner的答案中的代码是合法的。

不仅允许编译器,而且有责任删除副本。 ClangGCC的实时示例。 C ++ 17 6.3.2 / 2(强调我的意思):

  

[...] [注意:return语句可能涉及调用构造函数以执行操作数的复制或移动,如果它不是prvalue 或它的类型不同于函数的返回类型。如果返回了自动存储持续时间变量,则可以取消与return语句关联的复制操作或将其转换为move操作(10.9.5)。 —尾注]

这里的prvalue意味着一个临时值。有关确切的定义和大量示例,请参见here

在C ++ 11中,这确实是非法的。但这很容易解决,通过在return语句中使用括号初始化,您可以在调用站点位置构造返回值,从而完全绕开了经常被淘汰的复制构造函数的要求。 C ++ 11 6.6.3 / 2:

  

[...]带有括号初始化列表的return语句通过从指定的初始化程序列表进行复制列表初始化(8.5.4)初始化要从函数返回的对象或引用。 [...]

复制列表初始化意味着只调用构造函数。不涉及复制/移动构造函数。

ClangGCC的实时示例。

不幸的是,它们都不能在任何recent version of the Visual Studio compiler中工作,这很可悲,并且其不良C ++一致性的声誉很高。像这样返回不可复制的对象对于返回例如功能中的std::lock_guard个(允许您轻松地将std::mutex成员设为私有)等。

答案 2 :(得分:0)

从MSVC v19.14开始,MSVC(aka Visual Studio)还实现了C ++ 17行为,在适用RVO的情况下,允许return不可复制,不可移动的对象:{{ 3}}

正如rubenvb的回答所表明的,这意味着GCC,Clang和MSVC都支持以下C ++ 17行为:https://godbolt.org/z/fgUFdf