【问题标题】:Is it possible to return an instance of a non-movable, non-copyable type?是否可以返回不可移动、不可复制类型的实例?
【发布时间】:2015-12-14 09:50:24
【问题描述】:

在 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 特有的东西。我猜返回是有效的,因为构造返回值的需要可以被优化掉,所以智能感知警告会警告一些实际上不会发生的事情。

我想我在某处读到过,C++ 将标准化以确保始终会发生返回值优化。

那么,这是符合 C++ 标准的代码吗?它会在未来的编译器中继续工作吗?

附:我意识到std::mutexstd::lock_guard 可能会取代它。

【问题讨论】:

  • 标准 C++ 中的代码格式不正确,尽管有 proposed extension 可以使代码按预期运行。
  • 这似乎表明该扩展 (P0135R0) 已获得批准。我想我需要等待 C++17 编译器。 isocpp.org/blog/2015/11/kona-standards-meeting-trip-report
  • 不,P0135 处于早期阶段,离“批准”还很远。虽然它似乎很受欢迎,但它离最终形式还很遥远。供应商通常只执行明显的缺陷报告(甚至在批准之前),或者实际工作草案中超出上次发布标准的内容。
  • return {*this}; 而不是 return Lock(*this); 在 C++17 之前做你想做的事。

标签: c++ language-lawyer


【解决方案1】:

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

编译器不仅被允许,而且有义务删除副本。 ClangGCC 的实时示例。 C++17 6.3.2/2(重点是我的):

[...] [注意:如果操作数不是纯右值或者其类型不同于函数的返回类型。如果返回自动存储持续时间变量 (10.9.5),则可以省略与返回语句关联的复制操作或将其转换为移动操作。 ——尾注]

这里的纯右值与临时值一样多。确切的定义和大量示例,请参阅here

在 C++11 中,这确实是非法的。但它很容易补救,通过在 return 语句中使用大括号初始化,您在调用站点位置构造返回值,完美地绕过了经常省略的复制构造函数的要求。 C++11 6.6.3/2:

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

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

ClangGCC 的实时示例。 从 Visual Studio 编译器版本 16.14 开始,设置正确的语言标准 allows this code to be compiled

像这样返回不可复制的对象是一个非常强大的返回结构,例如std::lock_guards 来自函数(允许您轻松地将 std::mutex 成员保密)等。

【讨论】:

    【解决方案2】:

    如果编译成功,这是编译器中的一个错误。 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 意味着它不会被调用。

    【讨论】:

    • 正确。我检查过,即使 g++-5.1 也不能编译这段代码。结果:error: use of deleted function ‘Lock::Lock(Lock&&)’.
    【解决方案3】:

    从 MSVC v19.14 开始,MSVC(又名 Visual Studio)还实现了 C++17 行为,即在适用 RVO 的情况下允许 return 不可复制、不可移动的对象:https://godbolt.org/z/fgUFdf

    正如 rubenvb 的回答所示,这意味着 GCC、Clang 和 MSVC 都支持这种 C++17 行为:https://godbolt.org/z/Hq_GyG

    【讨论】:

      猜你喜欢
      • 2012-12-28
      • 2022-08-10
      • 2016-01-10
      • 2016-05-04
      • 2014-04-01
      • 2013-04-27
      • 1970-01-01
      • 1970-01-01
      • 2017-02-07
      相关资源
      最近更新 更多