Skip to content

关于本章

在阅读zhihu 如何评价 C++11 的右值引用(Rvalue reference)特性? - zihuatanejo的回答 - 知乎 时,其中的内容引起了我对C++ value semantic 和 reference semantic的思考,经过一番思考后,我发现,它们两者对于理解C++至关重要,需要进行专门的总结。

Guide

C++是兼容并包的

"C++是兼容并包的"语言,它既包含value semantic又包含reference semantic,而现代很多主流programming language仅仅只有reference semantic。

NOTE: 关于"C++是兼容并包的",参见 philosophy章节。

Value semantic and reference semantic是建立在object概念上的

Object对应的是storage,它是最最底层的概念,参见Object章节;

Every object has a value

Object章节中,对这个topic进行了讨论。

于此直接相关的是: object value representation(参见 Object章节),这是最最能够体现value semantic的。

Reference semantic: Reference to an object

只有先创建了一个object后,才能够reference它,否则是dangling;

reference value category
lvalue reference lvalue
rvalue reference rvalue

Value semantic and object lifetime and RAII

关于这一点,在 zhihu 如何评价 C++11 的右值引用(Rvalue reference)特性? - zihuatanejo的回答 - 知乎 中有着非常好的总结,后面收录了这篇文章。

What is value semantic and reference semantic?

从assignment的角度来进行区分

reference semantic 对应的是 bind、reference object;

value semantic 对应是 copy object;

下面是akrzemi1 Value semantics中的例子

In C++, A = B means that the value of B was assigned to A. In languages like C# and Java, A = B means that the reference A now points to whatever B was pointing to. Very different.

In C++, you can instantiate instances (objects) of a Class (user defined type) with the normal value semantics of the built in types. For example,

float a;
MyClass b;

We can also declare a pointer reference if we want:

MyClass* c = new MyClass();

In C#, we can only declare a reference:

MyClass c = new MyClass();

So, C# and Java have reduced functionality compared to C++.

Value semantic is default in C++

在C++中,value semantic is default,即默认是value semantic,如果要使用reference semantic,则需要使用&&&。而在java、python中则正好相反,reference semantic is default;

Runtime polymorphism

C++中需要通过reference、pointer才能够实现runtime polymorphism,我们将此成为call-by-reference-pointer,关于此,在下面章节中有说明:

1、C++\Language-reference\Classes\Subtyping-polymorphism章节进行了讨论。

2、call-by-reference-pointer

与call-by-reference-pointer相对的是call by value,在 call-by-reference-pointer 章节中,对两种进行了讨论。

Value category

这在C++\Language-reference\Expressions\Value-categories章节进行了讨论。

性能

value semantic和reference semantic和program的性能是存在一定关联的,这其中有着复杂的原因,其中一个非常重要的原因是: Compiler optimization,这在下面文章中进行了讨论:

1、C++\Language-reference\Basic-concept\Reference-and-Value-Semantics\Value-and-reference-semantics

zhihu 如何评价 C++11 的右值引用(Rvalue reference)特性? - zihuatanejo的回答 - 知乎

NOTE: C++ programming给予了programmer对object(或者更加彻底的说: memory、data)的control(比如control object lifetime),这是很多其他的programming language中没有的,并且C++使用object建立起了很多C++的核心特性,比如RAII等,这是C++相对其它programming language的一个特色、优势。这篇文章对这个topic的探讨是比较深刻的;

NOTE: 需要从control theory的角度来进行理解

在"object"章节对这个主题也有描述。

值语义是很多OO语言(比如python、java)里没有的概念。在这些语言里,几乎所有的变量都是引用语义(Reference Semantics),GC掌管了所有对象的生命期管理事务, 程序员无需为此操心,只需要用变量去对象池中去引用即可。值语义虽然也有出场的机会,例如Java里的Primitive Data Type,但毕竟不是重点,所以往往被忽视。

还有一个很重要的原因,在OO语言里,

值语义和运行时多态是矛盾的。

值语义因此也成为了C++区别于其它OO语言的特征之一。

所以,在不理解**值语义**的情况下去谈**右值引用**,最多也是浅尝辄止,所以"感觉奇怪"也是很正常的,甚至会出现类似“C++的数组不支持多态”?这样的问题。

值语义的作用

首先给出一篇blog作为值语义的科普,内容则不再复述,以下只讨论其意义。

这篇blog的作者Andrzej Krzemieński,曾在另一篇blog中盛赞RAII,并认为它是C++'s best featureHerb Sutter也在文章的回复中提到:

RAII code is just naturally exception-safe with immediate cleanup, not leaking stuff until the GC gets around to finding it. This is especially important with scarce resources like file handles.

但问题是,像C#using statement和Java的try-with-resources statement同样具有RAII的特点,但仍然有人会提出"RAII: Why is unique to C++?"这样的问题。原因即在于C++独有的值语义:**程序员通过**值语义可以方便直观地控制对象生命期(object lifetime),让RAII用起来更自然。

NOTE: C++ programmer能够control object lifetime。

更何况像这段代码,

vector<ifstream> produce()
{
  vector<ifstream> ans;
  for (int i = 0;  i < 10; ++i) {
    ans.emplace_back(name(i));
  } 
  return ans;
}

void consumer()
{
  vector<ifstream> files = produce();
  for (ifstream& f: files) {
    read(f);
  }
}

用try-with-resources根本就搞不定。当然,finally还是可以搞定,只是代码会很丑。

所以,**值语义**在C++里的作用之一就是用于控制对象生命期(另一个作用就是提升性能),以方便通过RAII写出简洁自然、异常安全的代码。该意义非常重大,这也是**右值引用**在C++ - State of the Evolution上稳坐头把交椅的原因。

右值引用与值语义

前面提到,**值语义**用于控制对象的生命期,而其具体的控制方式分为两种:

  • 生命期限于scope内:无需控制,到期自动调用析构函数。
  • 需要延长到scope外:移动语义。

因为右值引用的目的在于实现移动语义,所以**右值引用** 意义即是**加强了值语义对对象生命期的控制能力**。

在移动语义的作用下,Effective C++ 条款23从此作古:

当你必须传回object时,不要尝试传回reference。

资源管理类,如std::ifstreamboost::asio::basic_stream_socket,可以光明正大地当成int来用,而无需担心类似Effective STL 条款8那样的问题:

切勿创建包含auto_ptr的容器对象。

右值引用与完美转发

右值引用还有一个作用是实现完美转发。完美转发可以在一定程度上让代码保持简洁,但同时,这也引入了一些令人讨厌的坑。个人感觉意义不如移动语义的重大,所以这里不再展开了。

--------------- 附参考资料

C++发展概述

C++对value semantic的增强

1、copy elision

2、RVO

3、move semantic

下面是一个简单的总结:

feature 引入版本 章节 说明
move semantic C++11 参见C++\Language-reference\Reference\Move-semantic
Temporary materialization C++17 参见参见C++\Guide\Temporary

对reference semantic的增强

1、rvalue reference

TO READ

https://stackoverflow.com/questions/3106110/what-is-move-semantics/3109981#3109981

https://www.zhihu.com/question/22111546

https://isocpp.org/wiki/faq/value-vs-ref-semantics#virt-data

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm

https://herbsutter.com/2014/05/21/cppcon-my-proposed-talks-part-2/

http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2002/n1385.htm

https://marcbeauchesne.com/return-value-optimization-copy-elision/

https://www.fluentcpp.com/2016/11/28/return-value-optimizations/

https://stackoverflow.com/questions/18335861/why-is-enum-class-preferred-over-plain-enum

https://stackoverflow.com/questions/11711034/stdshared-ptr-of-this

https://stackoverflow.com/questions/142391/getting-a-boostshared-ptr-for-this