Effective C++(条款1-10)
条款1-4省略。
条款05 了解C++默默编写并调用哪些函数
如果一个类中没有任何函数,c++在创建该类时会默默编写并调用哪些函数呢?
首先如果类中没有没有任何函数,那么编译器会为该类声明一个拷贝构造函数、复制操作符、default构造函数、和析构函数;函数类型都是public且inline;
ps:
Class Empty{};
就相当于写下了以下代码
Class Empty{
public:
Empty(){} //default 构造函数
Empty(const Empty& rhs){} //拷贝构造函数
~Empty(){} //析构函数
Empty& operator=(const Empty& rhs) //复制操作符
};
那么有没有什么时候编译器是不会自动编写这些函数的吗?
1.如果编译器自动生成的默认的构造函数/析构函数不能满足该类的需求时,那么就需要在自己创建类时需要编写一个自己需要构造函数/析构函数,编译器就不会在为该类创建构造函数/析构函数;
2.如果类中有对象成员或者const成员,那么编译器会拒绝生成赋值函数;
拷贝构造函数和赋值函数的用法:
ps:
String a("hello"); String b("world"); String c = a;//这里c对象被创建调用的是拷贝构造函数 //一般是写成 c(a);这里是与后面比较 c = b;//前面c对象已经创建,所以这里是赋值函数
编译器生成的拷贝函数和赋值函数的缺点:
如果不主动编写的话,编译器将以“位拷贝”的方式自动生成缺省的函数。在类的设计当中,“位拷贝”是应当防止的。倘若类中含有指针变量,那么这两个缺省的函数就会发生错误。这就涉及到深复制和浅复制的问题了。
条款06 若不想使用编译器自动生成的函数,就应该明确拒绝
如果不希望class支持某一项特定机能机能,那么只要不声明相对应的函数就可以了,但是因为拷贝构造函数和赋值函数是可以被编译器
自动编写的并且是public的,所以正常一个calss是不能屏蔽拷贝机能的;
那么如果想写一个不支持拷贝类的需要怎么做呢?
1.首先想到的就是自己重写一个私有的拷贝构造函数和赋值函数,使编译器不会在自动编写这两个函数;也能阻止外部调用这两个函数;
但是这样的做法不能完全保证不会被调用到,因为成员函数和友元函数还是可以调用到你的私有函数;
怎么解决友元函数和成员函数的调用问题呢?
可以将函数只进行声明不实现该函数,因为这个时候如果有函数调用你没有实现的函数连接器会报错;
这样就能保证该class没有拷贝机能;
ps:
class NoCopy{ public: ..... private: NoCopy(cons tNoCopy& res); NoCopy& operator=(cons tNoCopy& res) }
2.上述办法只能在连接期发现错误,本着错误发现越早越好的想法,能不能将错误移到编译期呢?
设计一个专门阻止拷贝动作的base 类;将base类中的拷贝构造函数和赋值函数设置为私有的;
ps:
class NoCopyBase{ public: NoCopyBase(){} ~NoCopyBase(){} private: NoCopyBase(cons tNoCopy& res); NoCopyBase& operator=(cons tNoCopy& res) }
为了防止对象被拷贝我们只需要做的就是继承base类;
ps:
class NoCopyChild:privte NoCopyBase{ //不在声明 拷贝构造函数和赋值函数; }
这样只要任何人进行拷贝行为时编译器都会去调用base类中的拷贝赋值函数,因为base中的函数是私有的所以编译器会报错;
条款07 为多态基类声明virtual析构函数
如果基类的的析构函数不是虚函数的话,会造成什么影响,首先来看一段代码;
ps:
class Father
{
public:
Father(){cout<<"contructor Father!"<<endl;};
~Father(){cout<<"destructor Father!"<<endl;};
};
class Son:public Father
{
public:
Son(){cout<<"contructor Son!"<<endl;};
~Son(){cout<<"destructor Son!"<<endl;};
};
int main()
{
Father *pfather=new Son;
delete pfather;
pfather=NULL;
return 0;
}
运行结果:
contructor Father!
contructor Son!
destructor Father!
通过运行结果来看子类的构造函数已经被调用,但是在释放时并没有调用子类的析构函数;所以子类中成员变量也没有被销毁,造成了“局部销毁”;这样就会造成内存泄漏;
条款08 别让异常逃离析构函数
如果在析构函数中执行可能会抛出异常的操作,操作全部成功那么没有任何问题。
如果操作出现异常那么就是让这个异常逃离了析构,将这个异常抛了出去,再往下执行时可能会因为此次的异常造成不确定的风险,而且出现问题时很难调查出错误的原因;
解决办法:
1.如果程序碰到该异常,还能继续可靠的运行下去,可以在析构函数出现操作异常时,将该异常吞掉;
2.将析构函数的异常操作行为重新提供一个函数,使其调用者有机会在(析构函数之前)执行该操作出现异常时,可以处理该异常;(推荐使用)
条款09 绝不在构造和析构函数过程中调用虚函数
先看一段代码;
// 所有交易的基类 class Transaction { public: Transaction(); virtual void logTransaction() const = 0;//建立依赖于具体交易类型的登录项 }; //实现基类的构造函数 Transaction::Transaction() { logTransaction(); //最后,登录该交易 } // 派生类 class BuyTransaction : public Transaction { public: virtual void logTransaction() const; //怎样实现这种类型交易的登录?
}; //派生类 class SellTransaction : public Transaction { public: virtual void logTransaction() const; //怎样实现这种类型交易的登录?
};
当声明一个BuyTransaction对象的时候,首先Transaction的构造函数会被调用,从而其virtual函数也被调动,这里就是引发惊奇的起点。这时候被调用的logTransaction是Transaction的版本,而不是派生类BuyTransaction的版本。
所以并没有实现多态的功能;
切记!!!
在基类的构造过程中,虚函数调用从不会被传递到派生类中。派生类对象表现出来的行为好象其本身就是基类型。不规范地说,在基类的构造过程中,虚函数并没有被”构造”。也就是说,在派生类对象的基类子对象构造期间,调用的虚函数的版本是基类的而不是子类的。
对上面这种看上去有点违背直觉的行为可以用一个理由来解释-因 为基类构造器是在派生类之前执行的,所以在基类构造器运行的时候派生类的数据成员还没有被初始化。如果在基类的构造过程中对虚函数的调用传递到了派生类, 派生类对象当然可以参照引用局部的数据成员,但是这些数据成员其时尚未被初始化。这将会导致无休止的未定义行为。沿类层次往下调用尚未初始化的对象的某些部分本来就是危险的,所以C++干脆不让你这样做。
在对象的析构期间,存在与上面同样的逻辑。一旦一个派生类的析构器运行起来,该对象的派生类数据成员就被假设为是未定义的值,这样以来,C++ 就把它们当做是不存在一样。一旦进入到基类的析构器中,该对象即变为一个基类对象,C++中各个部分(虚函数,dynamic_cast运算符等等)都这样处理。
条款10 令operator=返回一个reference to *this
注意:
这只是个协议,并无强制性。若不遵循它,代码一样可通过编译。
令operator=返回一个reference to *this的目的:实现连锁赋值;
在代码中我们经常发现会出现以下类似的代码:
int x,y,z; x=y=z=15;
由于赋值是右结合律,所以a=b=c------->a=(b=(c = 15));
相当于先用c给b赋值,然后在用b给a赋值。
试想一下如果赋值函数写成
operator = (const ***& rhs){}
不会返回reference to *this;
那么上述的连续赋值操作将不被允许;
因为第一次赋值操作时没有返回对象指向左侧操符左侧的实参,所以在进行第二次操作赋值时,没有办法调用赋值函数,因为没有合适参数传入到赋值函数中;
如果自己定义的类也想实现连锁赋值那么也必须需要遵守该协议;
即:返回引用本体,而不能是一个副本,不然无法实现a=b=c这样的连锁赋值

浙公网安备 33010602011771号