为什么局部变量的常量会抑制返回值的语义?

时间:2013-04-18 17:41:29

标签: c++11 return const move-semantics rvalue-reference

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) );
}

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

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

此问题与另一个问题Move semantics & returning const values有关。不同之处在于,在后者中,声明函数返回const值。在我的示例中,FuncUsingConst被声明为返回易失性临时值。然而,函数体的实现细节会影响返回值的类型,并确定返回的值是否可以用作其他函数的参数。

标准是否符合此行为?
如何将其视为有用?

奖金问题:鉴于调用和实现可能在不同的翻译单元中,编译器如何知道编译时的差异?


编辑:试图重新解释这个问题。

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

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

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

2 个答案:

答案 0 :(得分: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;
}

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

<强>更新

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

const_cast

答案 1 :(得分:2)

  

如果某个对象的复制/移动构造函数被隐式使用并且无法访问特殊成员函数,则程序格式错误

- 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;
}

因为没有合法的方法可以将a转换为返回值。虽然可以省略返回,但是删除返回值并不会消除复制构造函数存在的要求。

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