为什么局部变量的常量禁止移动返回值的语义?

vsc*_*ech 15 const return rvalue-reference move-semantics c++11

struct STest : public boost::noncopyable {
    STest(STest && test) : m_n( std::move(test.m_n) ) {}
    explicit STest(int n) : m_n(n) {}
    int m_n;
};

STest FuncUsingConst(int n) {
    STest const a(n);
    return a;
}

STest FuncWithoutConst(int n) {
    STest a(n);
    return a;
}

void Caller() {
    // 1. compiles just fine and uses move ctor
    STest s1( FuncWithoutConst(17) );

    // 2. does not compile (cannot use move ctor, tries to use copy ctor)
    STest s2( FuncUsingConst(17) );
}
Run Code Online (Sandbox Code Playgroud)

上面的例子说明了如何在C++ 11中,如在Microsoft Visual C++ 2012中实现的,函数的内部细节可以修改其返回类型.直到今天,我的理解是返回类型的声明是程序员需要知道的,以了解如何处理返回值,例如,当作为参数传递给后续函数调用时.不是这样.

我喜欢const在适当的地方制作局部变量.它帮助我清理思路并清楚地构建算法.但要注意返回声明的变量const!即使变量将不再被访问(return毕竟执行了一个语句),即使声明的变量const已经超出范围(参数表达式的评估已经完成),它也无法移动,因此被复制(如果无法复制则无法编译).

这个问题与另一个问题有关,Move语义和返回const值.不同之处在于,在后者中,声明函数返回一个const值.在我的例子中,FuncUsingConst声明返回一个volatile临时的.然而,函数体的实现细节会影响返回值的类型,并确定返回的值是否可以用作其他函数的参数.

这种行为是否符合标准?
这如何被视为有用?

额外的问题:编译器如何知道编译时的差异,因为调用和实现可能在不同的翻译单元中?


编辑:尝试重新解释这个问题.

如果函数的结果多于声明的返回类型,那怎么可能?如果函数声明不足以确定函数返回值的行为,它甚至看起来是否可以接受?对我来说,似乎是FUBAR的一个案例,我只是不确定是否应该责怪标准或微软的实施.

作为被调用函数的实现者,我不能指望所有调用者都知道,更不用说监视调用代码中的每一个小变化.另一方面,作为调用函数的实现者,我不能依赖被调用的函数来返回在函数实现的范围内恰好被声明为const的变量.

功能声明是合同.现在有什么价值?我们不是在讨论语义上等效的编译器优化,比如copy elision,这很好但不会改变代码的含义.是否调用copy ctor 确实改变了代码的含义(甚至可以将代码破坏到无法编译的程度,如上所示).要理解我在这里讨论的尴尬,请考虑上面的"红利问题".

And*_*owl 16

我喜欢在适当的地方创建局部变量const.它帮助我清理思路并清楚地构建算法.

这确实是一种很好的做法.const尽可能使用.但是,在这里,您不能(如果您希望const移动对象).

const在函数内部声明一个对象的事实是,只要对象处于活动状态,就不会改变对象的状态 - 换句话说,在调用析构函数之前从未改变过.甚至调用析构函数之前也没有.只要它还活着,const物体的状态就不会改变.

但是,在这里,你可以某种程度上期望这个对象在它被淹没范围之前右边移动,并且移动正在改变状态.您无法从const对象移动- 即使您不再使用该对象也是如此.

但是,您可以做的是创建一个非const对象,并仅通过const绑定到该对象的引用在您的函数中访问它:

STest FuncUsingConst(int n) {
    STest object_not_to_be_touched_if_not_through_reference(n);
    STest const& a = object_not_to_be_touched_if_not_through_reference;

    // Now work only with a

    return object_not_to_be_touched_if_not_through_reference;
}
Run Code Online (Sandbox Code Playgroud)

通过一些规则,您可以轻松地强制执行函数创建后不应该修改该对象的语义 - 除了在返回时允许它从中移动.

更新:

正如balki在评论中所建议的那样,另一种可能性是将常量引用绑定到非const临时对象(其生命周期将根据§12.2/ 5延长),并const_cast在返回时执行:

STest FuncUsingConst(int n) {
    STest const& a = STest();

    // Now work only with a

    return const_cast<STest&&>(std::move(a));
}
Run Code Online (Sandbox Code Playgroud)

  • 这个怎么样?`const auto && obj = STest(n);` (2认同)
  • @vschoech:你的移动构造函数接受一个`STest const &&`什么都不做:你认为你会如何*实现*那个构造函数,如果你只有`const`的引用?您无法更改该对象的状态.这是`const`.不幸的是,目前我没有时间比我已经做的更广泛地回答你的问题了.此外,SO不是您在人们发帖的论坛.如果您不喜欢这个答案,请不要接受.如果您觉得需要就此答案提出进一步的问题,请发布一个新问题. (2认同)

Yak*_*ont 2

如果对象的复制/移动构造函数 [...] 被隐式使用 odr,并且特殊成员函数不可访问,则程序是格式错误的

-- n3485 C++ 标准草案 [class.copy]/30

我怀疑你的问题是 MSVC 2012,而不是 C++11。

这段代码,即使不调用它,也不是合法的 C++11:

struct STest {
  STest(STest const&) = delete
  STest(STest && test) : m_n( std::move(test.m_n) ) {}
  explicit STest(int n) : m_n(n) {}
  int m_n;
};

STest FuncUsingConst(int n) {
  STest const a(n);
  return a;
}
Run Code Online (Sandbox Code Playgroud)

因为没有合法的方法可以变成a返回值。虽然可以省略返回值,但省略返回值并不能消除复制构造函数存在的要求。

如果 MSVC2012 允许FuncUsingConst编译,则违反了 C++11 标准。

  • 我已经在对OP的评论中说过类似的话,你能帮我解释一下标准对此的说法吗?为 `return` 语句创建的临时对象只能被省略“当表达式是非易失性自动对象的名称 [...] 且具有与函数返回类型相同的 cv-unqualified 类型时”[class.copy] /31。我不确定“cv-_un_qualified”在这种情况下意味着什么(相同的资格或限定符被剥离)。 (2认同)
  • @DyP我读到的意思是“在删除该副本时可以忽略简历资格”,但是复制删除并不能消除要删除的副本结构存在的要求。我可能是错的:我现在正在尝试破译与之相关的标准术语。我认为 [class.copy]/30 满足了这个要求。“如果对象的复制/移动构造函数 [...] 被隐式使用 odr,并且特殊成员函数不可访问,则程序是格式错误的” (2认同)