cc++一些常见小错误

cc++一些常见小错误

记录一下,使用c/c++编码时常见的几个小误区。

一、指针的“1”

c/c++的指针非常灵活,在对不同类型的指针操作时,对指针的“1“的尺寸也会随着类型的变化而变化。例如对于4B尺寸的int类型,int*的”1“就是4B,在移动指针时,是以4B为”1“移动的,而不是1B。对于自定义类,这个”1“等于成员变量尺寸之和再经对齐后的大小。

二、几个运算符重载的常见误区

对自定义类的运算符函数重载,被重载的运算符一般都跟随再自定义类的后面。但++、--、+、-<<、>>运算符需要特别注意。++、--、+、-四种运算符重载时都可以不带参数,此时重载都是前置形式(虽然+、-没有加数和减数为空的后置形式)。++和--的前置和后置两种形式,无论那种都作用于唯一一个对象,即该对象本身(与+-=这些不同)。因此为了区分两种形式,c++有约定,当++运算符函数重载时的参数为int时,会将运算符函数解析为后置形式,此时这个int是个哑元,可以不包含形参(带了也无所谓),仅起到解析重载函数的作用。

<<、>>与=、+=这些类似,重载时至少需要一个参数,且都是默认的后置形式。不过,<<和>>常被用于数据流的输入和输出,如果只有后置形式,使用时难免会发生误解和歧义,例如重载后”obj << cout” 表示向标准输出流添加对象数据,这与平时的用法不同。如果想要运用常见的表示形式,那么重载函数需要使用到前面和后面两个参数来区分符号前后两个位置的数据。如果是在全局环境(c++中是对象外)下,这十分容易,但在自定义类中,每个成员函数都存在一个已填入的隐藏的参数本对象指针this,这不像python是显式给出的。因此我们需要使用friend(友元)函数来去除重载函数中的this指针,并保留对该类私有属性的访问权

三、隐藏的三次拷贝

在编译器早期版本,c++中在做参数传递时、返回值时(传递给临时变量/右值)、接收返回值时,会发生三次拷贝,现代编译器最多只有两次(参数传递时、接收返回值时)。对于现代编译器,返回值时一般不会自动调用拷贝构造,不过移动语义(c++11)和返回值优化(直接在函数被调用的位置构造临时/匿名对象存储返回值,而不是在函数内部构造,后续对返回值的接收也是发生在函数被调用的位置)出现后已经被简化了,但仍要注意左右值变化。

从上面例子可以看到,如果函数返回一个对作为左值的局部变量,是不会触发返回值优化的。原因在于更大一个方面:

先跳出对这个返回值优化的解释,左值可以被反复使用,如果接收者接收到一个左值,那么他就能够“窃取/转移“其资源(移动语义),造成后续其他对该左值的操作变为非法操作,移动是有风险的。为了防止这种情况,编译器变得很保守,规定:对左值,默认进行拷贝操作。相反的,对于临时性的右值,其资源不转移就会消失,因此默认使用移动语义(如果有的话),转移其资源。这样我们就能知道,为什么不对左值返回值优化(实际也是一种逻辑上的资源移动,不过被编译器简化)。

四、类初始化陷阱

在构造对象不需要参数时,使用括号对对象初始化容易被误解为函数声明。此时,应该使用

classname obj 或者 classname obj{}初始化函数,其中classname obj{}是更正式的做法。