C++ 性能剖析 (二):值语义 (value semantics)

 

Value Semantics (值语义) 是C++的一个有趣的话题。

 

什么是值语义? 简单的说,所有的原始变量(primitive variables)都具有value semantics. 也可以说,它们可以对应传统数学中的变量。有人也称它为POD (plain old data), 也就是旧时的老数据(有和 OOP 的新型抽象数据对比之意)。

 

对一个具有值语义的原始变量变量赋值可以转换成内存的bit-wise-copy。

 

对于用户定义的类呢?我们说,如果一个type X 具有值语义, 则:

1)X 的size在编译时可以确定。

     这一点看似自然,其实在C++里有许多变量的size编译时无法确定。比如我在reference 三位一体里提到的polymorphic 变量,因为是“多身份”的,其(内容)的size是动态的。

2)将X的变量x,赋值与另一个变量y,无须专门的 = operator,简单的bit-wise-copy 即可。

3)当上述赋值发生后,x和y脱离关系:x 和 y 可以独立销毁, 其内存也可以独立释放。

 

了解第三点很重要,比如下面的class A就不具备值语义:

class A

{

     char * p;

     public:

          A() { p = new char[10]; }

          ~A() { delete [] p; }

};

A 满足1和2,但不满足3。因为下面的程序会出错误: 

Foo()

{

    A a;

    A b = a;

} // crash here

 

改进的方法是定义一个A::operator=(constA &),并且用reference counting 的技术来保护指针,实现起来并不简单。所以我们说一旦一个class 失去了value semantics, 它也就失去了简单明了的 = 语义。

 

从上面的分析可以得出结论,value semantics 有个简单的 = , 也正是数学意义上的 =

 

学过Java, C#, 和JavaScript的程序员都知道,这些语言里的object都不具有值语义,因为它们都是指针,= 并不copy内容。也不满足条件3。

 

那么value semantics 对C++性能有什么影响呢?我觉得有以下几方面:

 

1)std 库是基于值语义 的。std container 包含的元素,都具有值语义. 不理解这一点,就不能正确使用std,也不会对std的性能,做出合理预期。

2)简单的bit-wise-copy 赋值语句一般会提高赋值性能,因为它不需要特殊的 = operator 了。在使用std container 时,会有大量的copy 或assignment。 bit-wise-copy对于小的变量通常比函数划算得多。

3)具有值语义的,size 不大的变量,在stack里,作为auto变量,传递,拷贝,释放全部和原始变量的用法完全一致,既好用,一般也具有优良的性能。动态语言缺乏这个(值语义)的语言构造和能力,(C# 有有限地支持:c# struct),所以速度上很难优化。

4)注意,在设计具有值语义的类时,不要保留无用的destructor. destructor 的存在,使得你的类的语义和原始类有了本质的区别,C++ 编译会为此处心积虑地添加管理代码,使得一个简单的函数复杂化(会专门著文论证), 并且严重影响性能。这些当然是有附加值的,但是必须是设计需求的,而不是简单照搬的。

5)不要保留无用的destructor这点,对使用 std 时更重要。我会专门论述。不要有臃余无用的 destructor 对任何类都是适用的,而对于有大量 copy, assignment 的 std container 尤为重要!

 

那么,什么样的类没有值语义呢?我们不妨称这种型为 none-value-semantics type (NVST).

 

1)有virtual function 的类

2)包含NVST成员的类

3)NVST 的衍生类(derived classed)

4)定义了自己的 = operator 的类

5)继承 virtual 基类的衍生类

 

2014-6-21 西雅图

你可能感兴趣的:(value)