关于本章
在阅读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 ofB
was assigned toA
. In languages like C# and Java,A = B
means that the referenceA
now points to whateverB
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 feature,Herb 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::ifstream、boost::asio::basic_stream_socket,可以光明正大地当成int
来用,而无需担心类似Effective STL 条款8那样的问题:
切勿创建包含
auto_ptr
的容器对象。
右值引用与完美转发
右值引用还有一个作用是实现完美转发。完美转发可以在一定程度上让代码保持简洁,但同时,这也引入了一些令人讨厌的坑。个人感觉意义不如移动语义的重大,所以这里不再展开了。
--------------- 附参考资料
- C++ future and the pointer. Jens Weller
- Back to the Basics! Essentials of Modern C++ Style. Herb Sutter
- Move Constructor. Andrzej Krzemieński
- A proposal to Add Move Semantics Support to C++ Language. Howard E. Hinnant, Peter Dimov, Dave Abrahams
- C++ 工程实践(8):值语义. 陈硕
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