通过使用`const T&'暂时解决了什么问题?

时间:2016-06-01 19:19:27

标签: c++ c++11 c++14

就像,如果我编写的代码如下所示:

Model.Equals()

我获得的代码有什么优势,而不是这样:

const int & const_value = 10;

int value = 10;

即使是更复杂的例子,例如

const int const_value = 10;

感谢copy elision,以下两个代码段不应该明显更快或消耗更少的内存。

const enterprise::complicated_class_which_takes_time_to_construct & obj = factory.create_instance();

enterprise::complicated_class_which_takes_time_to_construct obj = factory.create_instance();

是否有一些我遗漏的东西解释了这个结构的使用,或者我不考虑的隐藏的好处?

编辑:

@nwp提供的答案提供了一个工厂方法示例,如下所示:

const enterprise::complicated_class_which_takes_time_to_construct obj = factory.create_instance();

虽然这是一个很好的例子,说明为什么你一般需要精确的using cc = enterprise::complicated_class_which_takes_time_to_construct; struct Factory{ const cc &create_instance(){ static const cc instance; return instance; } } factory; 结构,但它没有回答为什么它需要处理临时数,或者为什么这个特定的结构会比只需将临时文件放在一个正确的堆栈分配对象中,编译器在正常情况下仍然可以复制 -

3 个答案:

答案 0 :(得分:2)

想象一下工厂是聪明的"并做类似

的事情
using cc = enterprise::complicated_class_which_takes_time_to_construct;
struct Factory{
    const cc &create_instance(){
        static const cc instance;
        return instance;
    }
} factory;

并且进一步假设cc不可复制,那么const &版本可以正常运行,而其他2版本则无法复制。对于实现缓存并保留对象的本地map并且您获得对地图内对象的引用的类来说,这是相当常见的。

编辑:

编码指南可能会说通过const & 从函数中获取返回值,因为它通常用于值和引用。

cc foo();
cc &foo();
const cc &foo();

如果你使用const cc &var = foo();,它将在所有情况下都没有不必要的副本。有时你会得到一个临时的,有时你不会,但代码总是做正确的事情,让你自由地改变实现,而不必关心你使用的函数如何返回它的返回值。在那里仍然存在const的问题,因此在所有情况下都不是优秀的符号,但它是一种可行的设计选择。

答案 1 :(得分:2)

有一个“病态”案例浮现在脑海中:不可复制和不可移动的物体。这样的对象不能按值存储,因此通过引用保存它是唯一的机会:

#include <iostream>

struct Immovable
{
    Immovable(int i) : i(i) {}

    Immovable(const Immovable&) = delete;
    Immovable(Immovable&&) = delete;

    const int i;
};


Immovable factory()
{
    return {42};
}


int main()
{
    const Immovable &imm = factory();
    std::cout << imm.i;
}

[Live example]

在一个不那么做作的说明中,你引用了复制省略来驳回“重物”的优化。但请注意,复制省略是可选的。您永远不能保证编译器会这样做。相反,将临时文件存储在const &中,保证不会发生复制或移动。

答案 2 :(得分:2)

终身扩展规则的基本原理已知¹(because Bjarne Stroustrup, the language creator, said so)具有统一的简单规则。特别是,在函数调用中用作实际参数的临时生存期延长到full-expression的结尾,覆盖了它所绑定的任何引用的生命周期。本地引用const或本地右值引用的规则使行为的这一方面相同。

在C ++ 03中,据我所知²invented by Petru Marginean,一个实际用例是创建一个未知自动推导类型的本地对象,如下所示:

Base const& o = foo( arg1, arg2, arg3 );

Base是任何可能的foo结果类型的可访问基类时,这是有效的,因为生命周期扩展机制不会切片:它会延长完整临时对象的生命周期。当foo返回T时,它是一个完整的T对象,其生命周期已延长。编译器知道T,即使程序员可能不一定知道T

当然,在C ++ 11及更高版本中,通常可以使用auto

 auto const o = foo( arg1, arg2, arg3 );

通过返回值优化和/或移动,这将与Marginean的技巧一样高效,并且更加清晰和简单。

但是,当类型T不可移动不可复制时,则绑定到具有临时扩展名的引用的引用,似乎是唯一可以保留的方式对它来说,这是第二个用例。

现在,出乎意料?,完整临时对象的类型T不需要从静态已知类型派生。编译器知道你持有对临时的部分的引用就足够了。该部分是基类子对象还是其他子对象无关紧要,因此您可以这样做:

auto const& part = foo( arg1, arg2, arg3 ).a_part;

这是第三个用例,它只是没有为整个对象引入一个从未使用过的名称,保持代码简单。

关于保持一个部分(以及整个对象)案例的标准:

C ++15§12.2/ 5 [class.temporary]
  

引用的临时值   绑定或临时是绑定引用的子对象的完整对象,在引用的生命周期内持续存在除外...

例外情况包括在函数调用中临时用作实际参数,持续到完整表达式结束,即比它所绑定的任何正式参数引用要长一点。

¹在an exchange with me in the Usenet group comp.lang.c++
²在Petru发明的ScopeGuard类的C ++ 03实现中。在C ++ 11中,ScopeGuard可以在客户端代码的dceclaration中使用std::functionauto轻松实现。 功能