为什么常量性的局部变量的抑制移动语义的返回值?(Why would const-ness of a

2019-08-31 11:45发布

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声明为返回挥发性暂时的。 然而,功能体的实现细节影响返回值的类型,并确定返回的值是否可被用作一个参数,以其它的功能。

这种行为是由标准的意图?
这怎么能算是有用吗?

奖金的问题:编译器怎样才能知道在编译时,给出呼叫和实现,可以在不同的翻译单位有什么区别?


编辑:尝试重新表述的问题。

这怎么可能,有更多比声明的返回类型的函数的结果呢? 它是如何甚至似乎在接受所有的函数声明不足以判断函数的返回值的行为? 对我来说,这似乎是FUBAR的情况下,我只是不知道是否责怪标准或微软的落实上。

作为调用函数的执行者,我不能指望知道所有呼叫者,更不用说监控调用代码的每个细微变化。 在另一方面,作为呼叫功能的执行者,我不能依靠所谓的功能来无回恰好是声明的函数实现的范围内的常量的变量。

一个函数的声明是一个合同。 这是什么值得呢? 我们不是在谈论一个语义上等价的编译器优化在这里,像复制省略,这是不错的,但不改变代码的含义。 是否将拷贝构造函数被称为确实改变的代码的含义(且甚至可以将代码破坏到一定程度,它不能被编译,如上所示)。 欣赏什么,我在这里讨论的重仓股,审议以上“奖金问题”。

Answer 1:

程序是形成不良的,如果复制/移动构造[...]为对象隐含使用的ODR和特殊成员函数是无法访问

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

我怀疑你的问题是与2012 MSVC,而不是与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成返回值。 虽然回报可以被省略,eliding返回值不会删除拷贝构造函数存在的需求。

如果MSVC2012被允许FuncUsingConst编译,它违反了C ++ 11标准的这样做。



Answer 2:

我喜欢在适当情况下进行局部变量常量。 它帮助我收拾了我的思路,并明确结构算法。

这的确是一个很好的做法。 使用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),并执行const_cast返回时,它:

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

    // Now work only with a

    return const_cast<STest&&>(std::move(a));
}


文章来源: Why would const-ness of a local variable inhibit move semantics for the returned value?