条款5:对定制的"类型转换函数"保持警觉
有两种函数可让编译器执行隐式转换,分别是单参数构造函数(single-argument constructors)和隐式类型转换运算符 。这种两种情况下根本问题是当你在不需要使用转换函数时,这些的函数缺却会被调用运行 。结果,这些不正确的程序会做出一些令人恼火的事情,而你又很难判断出原因。 单参数构造函数 是指只用一个参数即可以调用的构造函数(很多类都会有单参数的构造函数)。该函数可以是只定义了一个参数,也可以是虽定义了多个参数但第一个参数以后的所有参数都有缺省值。
class Name {
public :
Name ( const string& s) ;
. . .
} ;
class Rational {
public :
Rational ( int numerator = 0 ,
int denominator = 1 ) ;
. . .
} ;
隐式类型转换运算符 只是一个样子奇怪的成员函数:operator 关键字,其后跟一个类型符号。你不用定义函数的返回类型,因为返回类型就是这个函数的名字。
class Rational {
public :
. . .
operator double ( ) const ;
} ;
Rational r ( 1 , 2 ) ;
double d = 0.5 * r;
避免使用隐式类型转换运算符的方法 :提供功能等同的函数,显示调用
class Rational {
public :
. . .
double asDouble ( ) const ;
} ;
Rational r ( 1 , 2 ) ;
cout << r;
cout << r. asDouble ( ) ;
避免单参数构造函数被编译器用于隐式转换的方法1 :构造函数用explicit声明 ,如果这样做,编译器会拒绝为了隐式类型转换而调用构造函数。显式类型转换依然合法。下例中static_cast< Array<int> >(b[i])两个相邻的< <符合之间一定要加空格,C++把"<<"当作一个符合解释。
template < class T >
class Array {
public :
. . .
explicit Array ( int size) ;
. . .
} ;
Array< int > a ( 10 ) ;
Array< int > b ( 10 ) ;
if ( a == b[ i] ) . . .
if ( a == Array < int > ( b[ i] ) ) . . .
if ( a == static_cast < Array< int > > ( b[ i] ) ) . . .
避免单参数构造函数被编译器用于隐式转换的方法2 :根据规则“没有任何一个转换程序可以内含一个以上的用户定制转换行为”,因此可以在有可能被隐式转换的类型上套上一层,构建一个新class辅助使用。如下例,先要建立一个新类ArraySize。这个对象只有一个目的就是表示将要建立数组的大小,代替int直接表示,将int"套一层"。你必须修改Array的单参数构造函数,用一个ArraySize对象来代替int。
class Array {
public :
class ArraySize {
public :
ArraySize ( int numElements) : theSize ( numElements) { }
int size ( ) const { return theSize; }
private :
int theSize;
} ;
Array ( int lowBound, int highBound) ;
Array ( ArraySize size) ;
. . .
} ;
Array< int > a ( 10 ) ;
条款6:自增(increment)、自减(decrement)操作符前缀形式与后缀形式的区别
为了使得++ --前置和后置重载进行区分,规定后缀形式有一个int类型参数,当函数被调用时,编译器传递一个0做为int参数的值给该函数。
class UPInt {
public :
UPInt& operator ++ ( ) ;
const UPInt operator ++ ( int ) ;
UPInt& operator -- ( ) ;
const UPInt operator -- ( int ) ;
UPInt& operator += ( int ) ;
. . .
} ;
UPInt i;
++ i;
i++ ;
-- i;
i-- ;
熟悉前置后置的重载函数定义,前置形式有时叫做“先增加然后取回”,后置形式叫做“先取回然后增加”,因此前置式返回值为引用,后置式返回值为const临时对象 (返回临时对象是因为返回值是先前被取得值,而不是加1过后的值;const属性是为了避免"i++++"被合法化)。
UPInt& UPInt:: operator ++ ( )
{
* this += 1 ;
return * this ;
}
const UPInt UPInt:: operator ++ ( int )
{
UPInt oldValue = * this ;
++ ( * this ) ;
return oldValue;
}
由于前置式返回值为引用,后置式返回值为const临时对象(需要额外构造+析构),所以前置式的效率要高于后置式 。 为了保证前置和后置行为一致,后置操作以前置操作实现为基础而实现的 ,见上述两者函数定义中的代码。
条款7:千万不要重载&&,||和,操作符
C++对于“真假值表达式”采用“骤死式”评估方法:一旦该表达式的真假值(0或1)确定,及时表达式中还有部分尚未检验,整个评估工作仍宣告结束,效率会提高,评估顺序是从左到右 。如下例,这里不用担心当p为空时strlen无法正确运行,因为如果p为空,(p != 0) 为假,整个表达式已经可以确定为假,则strlen不会被调用。
char * p;
.. .
if ( ( p != 0 ) && ( strlen ( p) > 10 ) ) .. .
如果重载operator&& 和operator||,那么“函数调用”语义将会替代原本的“骤死式”语义。“函数调用”语义将会导致两个参数值均被计算,并且无法控制操作符两边的值谁会被先评估 。
if ( expression1 && expression2) .. .
对于编译器来说,等同于下面代码之一:
if ( expression1. operator && ( expression2) ) .. .
if ( operator && ( expression1, expression2) ) .. .
一个包含逗号的表达式首先计算逗号左边的表达式,然后计算逗号右边的表达式;整个表达式的结果是逗号右边表达式的值 。下例中,编译器首先计算++i,然后是—j,逗号表达式的结果是–j。
for ( int i = 0 , j = strlen ( s) - 1 ; i < j; ++ i, -- j)
. . * :: ? : && || ,
new delete sizeof typeid
static_cast dynamic_cast const_cast reinterpret_cast
你能重载:
operator new operator delete
operator new [ ] operator delete[ ]
+ - * / % ^ & | ~
! = < > += -= *= /= %=
^= &= |= << >> >>= <<= == !=
<= >= && || ++ -- , -> * ->
( ) [ ]
(有关new 和delete还有operator new , operator delete, operator new [ ] , and operator delete[ ] 的信息参见条款M8)
条款8:了解各种不同意义的new和delete
new operator 表示new操作符,它有两个步骤:(1)调用operator new(new操作)分配内存;(2)调用构造函数,初始化内存。不能重载 。
string * ps = new string ( "Memory Management" ) ;
上述操作等价于如下操作:
void * memory =
operator new ( sizeof ( string ) ) ;
call string :: string ( "Memory Management" )
on * memory;
string * ps =
static_cast< string * > ( memory) ;
delete operator 表示delete操作符,它有两个步骤:(1)调用析构函数;(2)调用operator delete(delete操作)释放内存 。不能重载。
delete ps;
导致编译器生成类似于这样的代码:
ps-> ~ string ( ) ;
operator delete ( ps) ;
operator new和operator delete只负责分配内存和释放内存,不会调用构造和析构函数 。可以重载。
void * rawMemory = operator new ( sizeof ( string ) ) ;
操作符operator new 将返回一个指针,指向一块足够容纳一个string 类型对象的内存。
void * buffer =
operator new ( 50 * sizeof ( char ) ) ;
.. .
operator delete ( buffer) ;
placement new 是operator new的一种,可以重载用来在一些已经分配好内存上构建对象 (让operator new不分配内存,直接返回指向内存的地址,对应上述new operator转换代码好理解)。
class Widget {
public :
Widget ( int widgetSize) ;
.. .
} ;
Widget * constructWidgetInBuffer ( void * buffer,
int widgetSize)
{
return new ( buffer) Widget ( widgetSize) ;
}
void * operator new ( size_t, void * location)
{
return location;
}
如果你用placement new在内存中建立对象,你应该避免在该内存中用delete operator 。因为delete operator调用operator delete来释放内存,但是包含对象的内存最初不是被operator new分配的,placement new只是返回转递给它的指针。如果要删除placement new创建的对象并释放内存,应该先用指针调用析构函数,之后再调用仅释放内存的函数 。
void * mallocShared ( size_t size) ;
void freeShared ( void * memory) ;
void * sharedMemory = mallocShared ( sizeof ( Widget ) ) ;
Widget * pw =
constructWidgetInBuffer ( sharedMemory, 10 ) ;
.. .
delete pw;
pw-> ~ Widget ( ) ;
freeShared ( pw) ;
当用new创建一个数组时,内存不再用operator new分配,代替以等同的数组分配函数,叫做operator new [] ,相对应的是operator delete []。operator new []和operator new均可以被重载 ,同理operator delete []也如此。 数组版的new operator 必须针对数组中每个对象调用一个构造函数,数组版的delete operator为每个数组元素调用析构函数,然后调用operator delete来释放内存 。
string * ps =
new string [ 10 ] ;
delete [ ] ps;
new和delete operator是内置的,其行为不受你的控制,但是它们调用的内存分配/释放函数则可以重载控制 。