正确命名的临时变量和右值-引用/移动

Proper named temporaries and rvalue-reference/move

本文关键字:引用 移动 变量      更新时间:2023-10-16

在c++ 11之前,作为一种标准编程习惯,临时变量通常被赋值给变量,以使代码更简洁。对于小字体通常是复制,对于大字体可能是引用,例如:

int a = int_func();
T const & obj = obj_func();
some_func( a, obj );

现在,将其与内联形式进行比较:

some_func( int_func(), obj_func() );
在c++ 11之前,

具有几乎相同的语义含义。随着右值引用和移动语义的引入,上述情况现在完全不同了。特别是,通过强制objT const &类型,您已经删除了使用move构造函数的能力,而内联形式的类型可以改为T&&

考虑到第一种是一种常见的范式,在标准中是否有任何东西允许优化器在第一种情况下使用move构造函数?也就是说,编译器是否可以忽略对T const &的绑定,而将其视为T&&,或者,正如我所怀疑的那样,这会违反抽象机器的规则吗?

问题的第二部分,要在c++ 11中正确地做到这一点(不消除命名临时),我们需要以某种方式声明一个正确的右值引用。我们也可以使用auto关键字。那么,正确的方法是什么呢?我猜是:

auto&& obj = obj_func();

第一部分:

编译器不允许隐式地将obj转换为非const右值,从而在调用some_func时使用move构造函数。

第2部分:

auto&& obj = obj_func();

这将创建一个对临时对象的非const引用,但在调用some_func时不会隐式移动它,因为obj左值。要将其转换为右值,您应该在调用站点使用std::move:

some_func( a, std::move(obj) );

我怀疑这是否可以优化,因为不清楚您是否会再次使用临时:

Foo x = get_foo(); // using best-possible constructor and (N)RVO anyway
do_something(x);
do_something_else(x);
//...

如果您真的热衷于利用移动语义(但一定要先配置文件,看看这真的很重要),您可以通过move:

显式地实现这一点。
Foo y = get_foo();
do_oneoff_thing(std::move(y));  // now y is no longer valid!

我想说的是,如果某些东西符合移动条件,那么您不妨自己进行内联,而不需要额外的局部变量。毕竟,如果这样一个临时变量只被使用一次,它有什么用呢?想到的唯一场景是,如果局部变量的最后使用可以利用move语义,那么您可以将std::move添加到最终外观中。不过,这听起来像是一个维护隐患,您真的需要一个令人信服的理由来编写它。

我不认为const &绑定到一个临时来延长生命周期是如此普遍。事实上,在c++ 03中,在许多情况下,只需按值传递并以您所谓的内联形式: some_func( int_func(), obj_func() )调用函数即可省略副本,因此您在c++ 11中注意到的相同问题将在c++ 03中发生(以略有不同的方式)

对于const引用绑定,如果obj_func()返回T类型的对象,那么上面的代码只是T obj = obj_func();的一种繁琐的方式,除了让人们好奇为什么需要之外,它没有提供任何好处。

如果obj_func()返回从T派生的类型T1,该技巧使您可以忽略确切的返回类型,但这也可以通过使用auto关键字来实现,因此在任何一种情况下,您所拥有的都是一个本地命名变量obj

obj传递给函数的正确方法——如果您完成了它,并且函数可以值从obj移动到一个内部对象,实际上应该是移动:

some_func( a, std::move(obj) );