`
varsoft
  • 浏览: 2570401 次
  • 性别: Icon_minigender_1
  • 来自: 上海
文章分类
社区版块
存档分类
最新评论

《C++0x漫谈》系列之:右值引用(或“move语意与完美转发”)(下)

阅读更多

C++0x漫谈》系列之:右值引用

或“move语意与完美转发”(下)

By 刘未鹏(pongba)

刘言|C++的罗浮宫(http://blog.csdn.net/pongba)

C++0x漫谈》系列导言

这个系列其实早就想写了,断断续续关注C++0x也大约有两年余了,其间看着各个重要proposals一路review过来:rvalue-referencesconceptsmemory-modelvariadic-templatestemplate-aliasesauto/decltypeGCinitializer-lists…

总的来说C++09C++98相比的变化是极其重大的。这个变化体现在三个方面,一个是形式上的变化,即在编码形式层面的支持,也就是对应我们所谓的编程范式(paradigm)C++09不会引入新的编程范式,但在对泛型编程(GP)这个范式的支持上会得到质的提高:conceptsvariadic-templatesauto/decltypetemplate-aliasesinitializer-lists皆属于这类特性。另一个是内在的变化,即并非代码组织表达方面的,memory-modelGC属于这一类。最后一个是既有形式又有内在的,r-value references属于这类。

这个系列如果能够写下去,会陆续将C++09的新特性介绍出来。鉴于已经有许多牛人写了很多很好的tutor这里这里,还有C++标准主页上的一些introductiveproposals,如这里,此外C++社群中老当益壮的Lawrence Crowl也在google做了非常漂亮的talk)。所以我就不作重复劳动了:),我会尽量从一个宏观的层面,如特性引入的动机,特性引入过程中经历的修改,特性本身的最具代表性的使用场景,特性对编程范式的影响等方面进行介绍。至于细节,大家可以见每篇介绍末尾的延伸阅读。

右值引用导言

右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一,这点从该特性的提案在C++ - State of the Evolution列表上高居榜首也可以看得出来。从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面的缺陷。从库设计者的角度讲,它给库设计者又带来了一把利器。从库使用者的角度讲,不动一兵一卒便可以获得“免费的”效率提升

完美转发

完美转发问题——不完美解决方案——模板参数推导规则——完美转发

动机

关于“完美转发”这个特性,其实提案N1385已经讲得非常清楚了,诸位可以直接去看N1385,如果实在还是觉得迷糊就再回来听我唠叨吧:-)

在泛型编码中经常出现的一个问题是(这个问题在实际中出现的场景很多,我们留到文章末尾再提,目前我们将这个特定的问题先提取孤立出来考虑):

如何将一组参数原封不动地转发给另一个函数

注意,这里所谓“原封不动”就是指,如果参数是左值,那么转发给的那个函数也要接受到一个左值,如果参数是右值,那么后者要接受到一个右值;同理,如果参数是const的,那么转发给的那个函数也要接受到一个const的值,如果是non-const的,那么后者也要接受到一个non-const的值。

总之一句话:

保持参数的左值/右值、const/non-const属性不变

听上去很简单吗?不妨试试看。

(不完美的)解决方案

假设我们要写一个泛型转发函数ff要将它的参数原封不动地转发给g(不管g的参数类型是什么):

template<typename T>

void f(/*what goes here?*/ t)

{

g(t);

}

上面的代码中,f的参数t的类型是什么?TT&const T&

我们一个个来分析。

Value

如果t的类型是T,即:

// take #1

template<typename T>

void f(T t)

{

g(t);

}

那么很显然,不能满足如下情况:

void g(int& i) { ++i; }

int myInt = 0;

f(myInt); // error, the value g() has incremented is a local value(a.k.a. f’s argument ‘t’)

即,不能将左值转发为左值。

Const&

如果t的类型为const T&,即:

// take #2

template<typename T>

void f(const T& t)

{

g(t);

}

则刚才的情况还是不能满足。因为g接受的参数类型为non-const引用。

Non-const&

那如果t的类型是T&呢?

// take #3

template<typename T>

void f(T& t)

{

g(t);

}

我们惊讶地发现,这时,如果参数是左值,那么不管是const左值还是non-const左值,f都能正确转发,因为对于const左值,T将会被推导为const UU为参数的实际类型)。并且,对于const右值,f也能正确转发(因为const引用能够绑定到右值)。只有对non-const右值不能完美转发(因为这时T&会被推导为non-const引用,而后者不能绑定到右值)。

即四种情况里面有三种满足了,只有以下这种情况失败:

void g(const int& i);

int source();

f(source()); // error

如果f是完美转发的话,那么f(source())应该完全等价于g(source()),后者应该通过编译,因为g是用const引用来接受参数的,后者在面对一个临时的int变量的时候应该完全能够绑定。

而实际上以上代码却会编译失败,因为f的参数是T&,当面对一个non-constint型右值(source()的返回值)时,会被推导为int&,而non-const引用不能绑定到右值。

好,现在的问题就变成,如何使得non-const右值也被正确转发,用T&f的参数类型是行不通的,唯一能够正确转发non-const右值的办法是用const T&来接受它,但前面又说过,用const T&行不通,因为const T&不能正确转发non-const左值。

Const& + non-const&

那两个加起来如何?

template<typename T>

void f(T& t)

{

g(t);

}

template<typename T>

void f(const T& t)

{

g(t);

}

一次重载。我们来分析一下。

对于non-const左值,重载决议会选中T&,因为绑定到non-const引用显然优于绑定到const引用(const T&)。

对于const左值,重载决议会选中const T&,因为显然这是个更specialized的版本。

对于non-const右值,T&根本就行不通,因此显然选中const T&

对于const右值,选中const T&,原因同第二种情况。

可见,这种方案完全保留了参数的左右值和const/non-const属性。

值得注意的是,对于右值来说,由于右值只能绑定到const引用,所以虽然const T&并非“(non-)const右值”的实际类型,但由于C++03只能用const T&来表达对右值的引用,所以这种情况仍然是完美转发。

组合爆炸

你可能会觉得上面的这个方案(const& + non-const&)已经是完美解决方案了。没错,对于单参的函数来说,这的确是完美方案了。

但是如果要转发两个或两个以上的参数呢?

对于每个参数,都有const T&T&这两种情况,为了能够正确转发所有组合,必须要2N次方个重载

比如两个参数的:

template<typename T1, typename T2>

void f(T1& t1, T2& t2) { g(t1, t2); }

template<typename T1, typename T2>

void f(const T1& t1, T2& t2) { g(t1, t2); }

template<typename T1, typename T2>

void f(T1& t1, const T2& t2) { g(t1, t2); }

template<typename T1, typename T2>

void f(const T1& t1, const T2& t2) { g(t1, t2); }

(完美的)解决方案

理想情况下,我们想要:

template<typename T1, typename T2, … >

void f(/*what goes here?*/ t1, /**/ t2, … )

{

g(t1, t2);

}

填空处应该填入一些东西,使得当t1对应的实参是non-const/const的左/右值时,t1的类型也得是non-const/const的左/右值。目前的C++03中,non-const/const属性已经能够被正确推导出来(通过模板参数推导),但左右值属性还不能。

明确地说,其实问题只有一个:

对于non-const右值来说,模板参数推导机制不能正确地根据其右值属性确定T&的类型(也就是说,T&会被编译器不知好歹地推导为左值引用)。

修改T&non-const右值的推导规则是可行的,比如对这种情况:

template<typename T>

void f(T& t);

f(1);

规定T&推导为const int&

但这显然会破坏既有代码。

很巧的是,右值引用能够拯救世界,右值引用的好处就是,它是一种新的引用类型,所以对于它的规则可以任意制定而不会损害既有代码,设想:

template<typename T >

void f(T&& t){ g(t); }

我们规定:

如果实参类型为右值,那么T&&就被推导为右值引用。

如果实参类型为左值,那么T&&就被推导为左值引用。

Bingo!问题解决!为什么?请允许我解释。

f(1); // T&& 被推导为 int&&,没问题,右值引用绑定到右值。

f(i); // T&& 被推导为 int&,没问题,通过左值引用完美转发左值。

等等,真没问题吗?对于f(1)的情况,t的类型虽然为int&&(右值引用),但那是否就意味着t本身是右值呢?既然t已经是具名(named)变量了,因此t就有被多次move(关于move语意参考上一篇文章)的危险,如:

void dangerous(C&& c)

{

C c1(c); // would move c to c1 should we allow treating c as a rvalue

c.f(); // disaster

}

在以上代码中,如果c这个具名变量被当成右值的话,就有可能先被move掉,然后又被悄无声息的非法使用(比如再move一次),编译器可不会提醒你。这个邪恶的漏洞是因为c是有名字的,因此可以被多次使用。

解决方案是把具名的右值引用作为左值看待

但这就使我们刚才的如意算盘落空了,既然具名的右值引用是左值的话,那么f(1)就不能保持1的右值属性进行转发了,因为f的形参t的类型(T&&)虽然被推导为右值引用(int&&),但t却是一个左值表达式,也就是说f(1)把一个右值转发成了左值。

最终方案

通过严格修订对于font-size:

分享到:
评论

相关推荐

    C++11右值引用和std::move语句实例解析(推荐)

    右值引用(及其支持的Move语意和完美转发)是C++0x将要加入的最重大语言特性之一。从实践角度讲,它能够完美解决C++中长久以来为人所诟病的临时对象效率问题。从语言本身讲,它健全了C++中的引用类型在左值右值方面...

    C++11右值引用和转发型引用教程详解

    C++11标准引入了右值引用(rvalue reference)和转发型引用(forwarding reference),解决了移动语义和完美转发问题。本教程将详细介绍右值引用和转发型引用的概念、类型、语法、用法和应用场景。 一、右值引用...

    C++中的`std::move`与`std::forward`:完美转发与移动语义的精髓

    `std::move`用于将左值转换为右值引用,而`std::forward`用于保持参数的原始类型(左值或右值)。`std::move`通常用于需要移动语义的场景,如移动构造函数和移动赋值运算符,而`std::forward`则主要用于模板编程中,...

    深入理解C++中的左值引用和右值引用

    右值引用在现代C++编程中主要用于实现移动语义、完美转发以及优化性能。例如,可以使用右值引用将对象从一个地方移动到另一个地方,而不是拷贝,这在处理大型数据结构时尤其重要。 移动语义的引入允许编译器进行...

    右值引用、移动语义和完美转发1

    在C++编程语言中,右值引用和移动语义是C++11标准引入的重要特性,它们极大地提升了程序性能,特别是在处理临时对象和资源管理时。本文将详细探讨这两个概念,以及它们如何与左值引用和左值、右值相互作用。 首先,...

    31_c++中的左值引用与右值引用1

    在C++编程语言中,左值引用和右值引用是两个关键的概念,它们与对象的生命周期、内存管理和性能优化紧密相关。左值引用和右值引用的区分主要基于对象是否可寻址和是否临时。 左值(Left-Value)是指可以被赋值并且...

    Overview of the New C++ C++0x Scott Meyers

    - **多线程库**:C++0x提供了标准的多线程支持,包括`std::thread`类、原子操作(`std::atomic`)等。 - **优点**:提高了编写并发程序的能力,使得多线程编程更加安全、高效。 - **示例**:创建两个线程,分别执行...

    浅析C++11中的右值引用、转移语义和完美转发

    C++11是一个重要的语言版本,它引入了许多新特性,其中右值引用、移动语义和完美转发是提升效率和代码质量的关键元素。本文将详细解释这三个概念。 首先,我们来了解一下左值与右值的概念。左值(lvalue)是指可以...

    《The C++ Programming Language for C++0x》雏形(C++0x决议草案,源于C++之父网站主页)

    - **右值引用**:引入了右值引用的概念,支持移动语义和完美转发,提高了性能并减少了临时对象的复制开销。 - **lambda表达式**:增加了lambda表达式的支持,允许开发者在不定义新函数的情况下创建匿名函数,大大...

    c++11 右值引用和移动语义(csdn)————程序.pdf

    右值引用主要用于移动语义和完美转发,前者需要有修改右值的权限,const 可使用左值引用。 左值和右值的概念: * 左值:左值表达式结束后仍然存在的持久对象,可以对表达式用&取地址;变量,可以放在=的左边(也...

    VC10中的C++0x特性

    在Microsoft Visual Studio 2010 (VC10)中,C++编译器开始支持C++11标准的一部分,通常称为C++0x。这个版本的编译器引入了许多新特性,极大地增强了C++语言的功能性和现代性。以下是关于VC10中C++0x特性的详细解释:...

    关于c++的 右值 右值引用 move

    C++中的右值和右值引用是现代C++的一个重要特性,主要用来提高程序的效率,特别是涉及到对象拷贝和赋值操作时。在讲解右值和右值引用之前,我们先来理解左值和左值引用的概念。 左值(Left-Value)是指可以取地址的...

    C++11 标准新特性_ 右值引用与转移语义

    C++11的新特性。尤其是lamda表达式,使得C++灵活了很多

    C++0x_新特性.pdf

    右值引用的主要目的是实现移动语义(move semantics)和完美转发(perfect forwarding)。移动语义允许对象在需要时将资源从一个对象转移到另一个对象,而不需要进行深拷贝,从而优化性能。完美转发是指函数模板可以将...

    深入理解c++右值引用1

    此外,C++11还引入了右值引用的转发引用,它是一种特殊的右值引用,可以作为模板参数,可以适应任意类型的左值或右值,这在编写泛型代码时非常有用。 总之,右值引用是C++11中的一项重要创新,它提高了程序的效率,...

    C++11:右值引用-附件资源

    C++11:右值引用-附件资源

    Overview of the New C++ (C++0x)

    右值引用是C++0x中另一个重要的概念,它主要用于支持移动语义和完美转发。移动语义允许在构造或赋值过程中,将一个临时对象的状态快速地转移到另一个对象中,而无需进行复制。这在处理大型对象时可以极大地提高性能...

    C++11 模板参数的“右值引用”是转发引用吗

    C++11 模板参数的“右值引用”是转发引用吗? C++11 中引入了右值引用(Rvalue Reference),它的主要用途是转发引用(Forwarding Reference)。但是什么是转发引用?如何使用它?本文将详细介绍 C++11 模板参数的...

Global site tag (gtag.js) - Google Analytics