15
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早已超出范围(参数表达式的评估已完成),它也无法移动,因此将被复制(如果无法复制,则编译失败)。

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

这种行为是否符合标准?
这怎么能算有用呢?

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


编辑:试图改写这个问题。

一个函数的结果怎么可能比声明的返回类型更多?函数声明不足以确定函数返回值的行为,这似乎完全可以接受吗?对我来说,这似乎是 FUBAR 的一个例子,我只是不确定是否应该责怪标准或微软的实施。

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

函数声明是一个契约。现在值多少钱?我们在这里讨论的不是语义等价的编译器优化,比如复制省略,这很好,但不会改变代码的含义。无论是否调用了 copy ctor 都会改变代码的含义(甚至会破坏代码到无法编译的程度,如上图所示)。要理解我在这里讨论的尴尬,请考虑上面的“奖金问题”。

4

2 回答 2

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在评论中所建议的那样,另一种可能性是将常量引用绑定到非常量临时对象(根据第 12.2/5 节,其生命周期将延长),并const_cast在返回时执行 a :

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

    // Now work only with a

    return const_cast<STest&&>(std::move(a));
}
于 2013-04-18T18:20:37.343 回答
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;
}

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

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

于 2013-04-19T19:51:27.343 回答