effective C++ 3rd 笔记(一)
2011-04-05 21:12
169 查看
条款01:视C++为一个语言联邦 : C, Object-Oriented C++, Template C++, STL
条款02:尽量以const,enum,inline 替换#define:
1.对于单纯常量,最好以const对象或enum替换#define
2.对于形似函数的宏(macros),最好改用inline函数替换#define。
#define ASPECT_RATIO 1.653
const double AspectRatio = 1.653;
1)用#define定义的名字可能没有进入记号表,编译出错,不方便追踪
2)用const定义常量将得到更精简的目标代码(object code),因为预处理器处理#define是会盲目的将宏名称替换为1.653导致目标码出现多份1.653若用const则不会出现这种问题。
当用常量替换#define有两种特殊的情况。
1)定义常量指针。由于常量定义式通常放在头文件内(以便被不同源码含入),因此有必要将指针声明为const。 const char* const authorName = “Scott Meyer”; string对象通常比char*-base合宜,所以authorName往往定义成这样:const std::string authorName(“Scott Meyer”); 2)class专属常量。为了将常量的作用域限制于class内,和确保此常量至多只有一份实体,必须让它成为一个static成员: class GamePlayer { private: static const int NumTurns = 5; int scores[NumTurns]; ... } 通常static & const& 整数类型(ints,chars,bools)类型成员如果不取地址,可以声明并使用而无需定义。但如果你取某个class专属常量的地址,或你的编译器却(不正确地)坚持要看到一个定义式就必须提供如下定义式 const int GamePlayer::NumTurns; //放到实现文件,因为在声明时已获得初值,不能再设初值。 我们无法用#define 创建class专属常量,因为#define不重视作用域scope。 旧时编译器也许不支持上述语法,不允许static成员在声明时提供初值。 则将初值放到定义式里,但编译器坚持必须在编译期间知道数组score[]大小,万一你的编译器不允许static 整数型class常量完成 in class初值设定,可改用 the enum hack补偿做法。 class GamePlayer{ private: enum{ NumTurns=5 }; int scores[NumTurns]; } 1)取enum和#define地址一样,不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。enums和#defines一样不会导致非必要的内存分配。 2)实用,很多代码用了。 enum hack是 模板元编程template metaprogramming的基础技术。 另一个常见的#define误用情况是用它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销。 #define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次 替换为内联模板函数:宏的效率和类型安全,可预料行为
template<typename T>
inline void MAX(const T&a,const T&b)
{
f(a>b?a:b);
} 条款03: 尽可能实用const const Rational operator* (const Rational& lhs, const Rational& rhs);//为什么返回const对象? Rational a, b, c; … (a * b) = c;// if(a * b = c) … // 其实想做比较操作,返回const可预防此类问题 const成员函数:可被const对象调用。两个函数如果只是常量性不同,是可以被重载。 可用mutable释放掉non-static成员变量的bitwise constness约束(即mutable修饰的成员变量可被const成员函数修改)。 在const和non-const成员函数中避免重复: class TextBlock { public: … const char& operator[] (std::size_t position) const { … return text[position]; } char& operator[] (std::size_t position) { //non-const成员函数调用const成员函数避免代码重复 return const_cast<char&>( static_cast<const TextBlock&>(*this) [position]); } private: std::string text'; }; 不能令const成员函数调用non-const成员函数,对象有可能被改动。 条款04: 确定对象被使用前已先被初始化。 1.对内置类型手工初始化 2.构造函数最好使用成员初值列 member initialization list,而不要在构造函数体内用赋值操作。初值列中列出的成员变量次序要跟在class中声明次序一致。 3.为免除跨编译单元之初始化次序问题,用local static 对象替换non-local static 对象 class A { public: A(const string&); private: string str; int num; } A::A(const string &s){ str=s; //赋值而非初始化,在初值列中调用一次default构造函数,函数体内一次赋值操作 num=0'; //内置类型不保证。 } C++规定对象的成员变量初始化动作发生在进入构造函数体之前。如果成员变量是const或引用,则一定要初始化,而不能被赋值。 A::A(const string &s):str(s), //值调用一次copy构造函数 num(0){} static对象:从被构造到程序结束为止。main()结束时自动析构。 包括global对象,namespace内的对象,在class内、函数内、及在file作用域内被声明为static的对象。 local static对象:函数内的static对象 non-local static对象:其他 如果某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,则可能未被初始化。C++对”定义于不同便一单元内的non-local static对象“的初始化次序无明确定义。 class FileSystem { public: … std::size_t numDisks() const; }; extern FileSystem tfs; //另一编译单元 class Directory { public: Directory(params); }; Directory::Directory(params) { std::size_t disks = tfs.numDisks(); } Directory tempDir (params); 解决方案:singleton模式,用local static对象替换non-local static对象 C++保证,函数内的local static 对象会在该函数被调用期间,首次遇上该对象定义式时被初始化。 class FileSystem {…}; FileSystem& tfs() { static FileSystem fs; return fs;} class Directory {…}; Directory::Directory (params) { std::size_t disks = tfs().numDisks(); }; Directory& tempDir() { static Directory td; return td;} 条款05:了解C++默默编写并调用哪些函数 编译器暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数 只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。 如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。 template<class T> class NamedObject { public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){} private: string &nameValue; const T objectValue; }; string newDog(“aaa”); string oldDod(“bbb”); NamedObject<int> p(newDog,2); NamedObject<int> s(oldDog, 36); p = s; //编译失败 在内含引用或const成员的class内支持赋值操作符,你必须自己定义copy assignm操作符。 如果某个base class将copy assignment操作符声明为private,编译器拒绝为其derived classes生成一个copy assignment操作符。 条款06: 若不想使用编译器自动生成的函数,就该明确拒绝。 方案:将相应成员函数声明为private并不予实现。或使用Uncopyable这样的base class 明确声明一个成员函数,阻止编译器暗自生成,另其为private,阻止外部调用。 但成员函数和friend仍然可以反问,所以只声明而不定义它们,如不慎调用,则会得到一个连接错误。 将连接期错误移到编译器: class Uncopyable{ protected: Uncopyable(){} //允许derived对象构造和析构 ~Uncopyablel(){} private: Uncopyable(const Uncopyable&); //但阻止copying Uncopyable& operator=(const Uncopyable&); }; class HomeForSale:private Uncopyable { …}; 当尝试拷贝HomeForSale对象时,自动生成copy构造函数和copy assignment操作符,并尝试调用基类对于版本,但失败。。。 条款07: 为多态基类声明virtual析构函数
1.若果class带有任何virtual函数,它就应该拥有一个virtual析构函数
2.class的设计目的不是作为base class使用或不是为了具备多态性,就不改声明为virtual析构函数
C++明确指出当derived对象经过base class指针被删除,而该base class带有一个non-virtual析构函数,则其结果未定义的:局部销毁。
每一个带有virtual函数的class都有一个相应的由函数指针构成的数组vtbl(virtual table). 当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr (virtual table pointer)所指的那个vtbl,编译器在其中寻找适当的函数指针。无端声明析构函数为virtual会增加为vptr的付出的空间补偿。
class SpecialString: public std::string{ //馊主意!
};
SpecialString *pss = new SpecialString(“aaa”);
std::string *ps=pss;
delete ps;//未定义!
如果企图继承一标准容器或任何其他带有non-virtual析构函数的class,拒绝诱惑吧!!!
为你希望成为抽象(不能实体化)的那个class声明一个pure virtual析构函数
class AWOV {
public: virtual ~AWOV() = 0;
};
必须提供一份定义 AWOV::AWOV() {}
不然派生类的析构函数调用base class的析构函数时,不然连接器会报错。
条款08:别让异常逃离析构函数
1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能突出异常,析构函数应该捕捉任何异常,然后吞下(不传播)或结束程序。
2.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。
栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。在执行析构函数前如果已经有一个异常,析构函数中又抛出一个异常,将会导致调用标准库 terminate 函数强制从程序非正常退出。
class DBConnection {
public : static DBConnection create();
void close();
};
确保DBConnection不忘记调用close()
class DBConn {
public:
~DBConn(){
db.close();}
private: DBConnection db;
};
DBConn dbc(DBConnection::create());
如果析构函数抛出异常:
1) 强迫结束
DBConn::~DBConn(){
try { db.close();}
catch(…) {
制作运转记录,记下对close的调用失败
std::abort();
}
2)吞下异常
DBConn::~DBConn(){
try { db.close();}
catch(…) {
制作运转记录,记下对close的调用失败
}
3)较佳的策略,客户自己调用普通函数
class DBConn {
public:
void close(){
db.close();
closed = true;
}
~DBConn(){
if(!closed) {
try { db.close(); }
catch(…){
制作运转记录,记下对close的调用失败
abort或吞下异常
}
}
private: DBConnection db;
bool closed;
};
条款09:绝不在构造和析构函数中调用virtual函数
因为这类调用从不下降至derived class (比起当前执行构造函数和析构函数的那层)
class Transaciton {
public:Transaciton();
virtual void logTransaciton() const = 0;
};
Transaciton::Transaciton(){
logTransaciton();
}
class BuyTransaciton:public Transaciton{
public:
virtual void logTransaciton() const;
};
class SellTransaciton:public Transaciton{
public:
virtual void logTransaciton() const;
};
BuyTransaciton b; //基类构造函数调用的是Transaciton::logTransaciton();
BuyTransaciton专属成分还未初始化,最安全的做法就是视而不见,当成Transaciton类。
对象在derived class 构造函数开始执行前不会成为一个derived class。
析构函数类似,在进入base class 析构函数后对象就成为一个base class对象。
解决方案:令derived class 将必要的构造信息上传至base class构造函数,来替换无法使用virtul函数从base class向下调用。
class Transaciton {
public:
explicit Transaciton(const string &loginfo);
void logTransaciton(const string &loginfo) const;
};
Transaciton::Transaciton(const string &loginfo){
logTransaciton(loginfo);
}
class BuyTransaciton:public Transaciton{
public:
BuyTransaciton( parameters): Transaciton( createLogString(parameters)) {…}
private:
static string createLogString(parameters);
};
条款10: 令operator= 返回一个reference to *this
条款11: 在operator= 中处理自我赋值
class Bitmap {…};
class Widget {
private: Bitmap *pb;
};
Widget& Widget::operator=(const Widget &rhs) {
delete pb; //没有进行自我赋值检测
pb = new Bitmap(*rhs.pb);//不具备异常安全,发生异常后,pb指向一块已删除的内存
return *this;
}
解决:证同测试
Widget& Widget::operator=(const Widget &rhs) {//不具备异常安全
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);//不具备异常安全
return *this;
}
Widget& Widget::operator=(const Widget &rhs) {
Bitmap *pOrig = pb; //异常安全,又能处理自我赋值,当自我赋值发生频率较高时,可加入证同测试。
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
另一替换方案确保异常安全,又能处理自我赋值:copy and swap技术
class Widget{
void swap(Widget &rhs);
};
Widget& Widget::operator=(const Widget &rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
Widget& Widget::operator=(const Widget rhs){ //另一较高效写法,by-value
swap(rhs);
return *this;
}
条款12: 复制对象时勿忘其每一个成分
1.copying 函数应该确保赋值”对象内所有成员变量”及“所有base class成分”
2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个函数共同调用。
为derived class编写copying函数时,必须复制其base class成分。
class Customer {
public:
Customer(const Customer &rhs);
Customer& operator=(const Customer &rhs);
private: string name;
};
class PriorityCustomer: public Customer {
public:
priorityCustomer(const priorityCustomer &rhs);
priorityCustomer& operator=(const priorityCustomer &rhs);
private: int priority;
};
priorityCustomer::priorityCustomer(const priorityCustomer &rhs)
:Customer(rhs), //调用base class的copy构造函数,如省略,则调用默认的构造函数,基类部分为默认值
priority(rhs.priority)
{ }
priorityCustomer& priorityCustomer::operator=(const priorityCustomer &rhs) {
Customer::operator=(rhs); //可先进行证同测试,成员如有指针
priority = rhs.priority;
return *this;
}
条款02:尽量以const,enum,inline 替换#define:
1.对于单纯常量,最好以const对象或enum替换#define
2.对于形似函数的宏(macros),最好改用inline函数替换#define。
#define ASPECT_RATIO 1.653
const double AspectRatio = 1.653;
1)用#define定义的名字可能没有进入记号表,编译出错,不方便追踪
2)用const定义常量将得到更精简的目标代码(object code),因为预处理器处理#define是会盲目的将宏名称替换为1.653导致目标码出现多份1.653若用const则不会出现这种问题。
当用常量替换#define有两种特殊的情况。
1)定义常量指针。由于常量定义式通常放在头文件内(以便被不同源码含入),因此有必要将指针声明为const。 const char* const authorName = “Scott Meyer”; string对象通常比char*-base合宜,所以authorName往往定义成这样:const std::string authorName(“Scott Meyer”); 2)class专属常量。为了将常量的作用域限制于class内,和确保此常量至多只有一份实体,必须让它成为一个static成员: class GamePlayer { private: static const int NumTurns = 5; int scores[NumTurns]; ... } 通常static & const& 整数类型(ints,chars,bools)类型成员如果不取地址,可以声明并使用而无需定义。但如果你取某个class专属常量的地址,或你的编译器却(不正确地)坚持要看到一个定义式就必须提供如下定义式 const int GamePlayer::NumTurns; //放到实现文件,因为在声明时已获得初值,不能再设初值。 我们无法用#define 创建class专属常量,因为#define不重视作用域scope。 旧时编译器也许不支持上述语法,不允许static成员在声明时提供初值。 则将初值放到定义式里,但编译器坚持必须在编译期间知道数组score[]大小,万一你的编译器不允许static 整数型class常量完成 in class初值设定,可改用 the enum hack补偿做法。 class GamePlayer{ private: enum{ NumTurns=5 }; int scores[NumTurns]; } 1)取enum和#define地址一样,不合法。如果你不想让别人获得一个pointer或reference指向你的某个整数常量,enum可以帮助你实现这个约束。enums和#defines一样不会导致非必要的内存分配。 2)实用,很多代码用了。 enum hack是 模板元编程template metaprogramming的基础技术。 另一个常见的#define误用情况是用它实现宏。宏看起来像函数,但不会招致函数调用带来的额外开销。 #define MAX(a,b) f((a)>(b)?(a):(b))
int a =5, b = 0;
MAX(++a, b);//a被累加二次
MAX(++a, b+10);//a被累加一次 替换为内联模板函数:宏的效率和类型安全,可预料行为
template<typename T>
inline void MAX(const T&a,const T&b)
{
f(a>b?a:b);
} 条款03: 尽可能实用const const Rational operator* (const Rational& lhs, const Rational& rhs);//为什么返回const对象? Rational a, b, c; … (a * b) = c;// if(a * b = c) … // 其实想做比较操作,返回const可预防此类问题 const成员函数:可被const对象调用。两个函数如果只是常量性不同,是可以被重载。 可用mutable释放掉non-static成员变量的bitwise constness约束(即mutable修饰的成员变量可被const成员函数修改)。 在const和non-const成员函数中避免重复: class TextBlock { public: … const char& operator[] (std::size_t position) const { … return text[position]; } char& operator[] (std::size_t position) { //non-const成员函数调用const成员函数避免代码重复 return const_cast<char&>( static_cast<const TextBlock&>(*this) [position]); } private: std::string text'; }; 不能令const成员函数调用non-const成员函数,对象有可能被改动。 条款04: 确定对象被使用前已先被初始化。 1.对内置类型手工初始化 2.构造函数最好使用成员初值列 member initialization list,而不要在构造函数体内用赋值操作。初值列中列出的成员变量次序要跟在class中声明次序一致。 3.为免除跨编译单元之初始化次序问题,用local static 对象替换non-local static 对象 class A { public: A(const string&); private: string str; int num; } A::A(const string &s){ str=s; //赋值而非初始化,在初值列中调用一次default构造函数,函数体内一次赋值操作 num=0'; //内置类型不保证。 } C++规定对象的成员变量初始化动作发生在进入构造函数体之前。如果成员变量是const或引用,则一定要初始化,而不能被赋值。 A::A(const string &s):str(s), //值调用一次copy构造函数 num(0){} static对象:从被构造到程序结束为止。main()结束时自动析构。 包括global对象,namespace内的对象,在class内、函数内、及在file作用域内被声明为static的对象。 local static对象:函数内的static对象 non-local static对象:其他 如果某个编译单元内的某个non-local static对象的初始化动作使用了另一编译单元内的某个non-local static对象,则可能未被初始化。C++对”定义于不同便一单元内的non-local static对象“的初始化次序无明确定义。 class FileSystem { public: … std::size_t numDisks() const; }; extern FileSystem tfs; //另一编译单元 class Directory { public: Directory(params); }; Directory::Directory(params) { std::size_t disks = tfs.numDisks(); } Directory tempDir (params); 解决方案:singleton模式,用local static对象替换non-local static对象 C++保证,函数内的local static 对象会在该函数被调用期间,首次遇上该对象定义式时被初始化。 class FileSystem {…}; FileSystem& tfs() { static FileSystem fs; return fs;} class Directory {…}; Directory::Directory (params) { std::size_t disks = tfs().numDisks(); }; Directory& tempDir() { static Directory td; return td;} 条款05:了解C++默默编写并调用哪些函数 编译器暗自为class创建default构造函数,copy构造函数,copy assignment操作符,以及析构函数 只有当一个类没有定义构造函数时,编译器才会自动生成一个默认构造函数。 如果没有定义复制构造函数,编译器就会合成一个,即使已经定义了其他的构造函数。 template<class T> class NamedObject { public: NamedObject(string &name, const T &value):nameValue(name),objectValue(value){} private: string &nameValue; const T objectValue; }; string newDog(“aaa”); string oldDod(“bbb”); NamedObject<int> p(newDog,2); NamedObject<int> s(oldDog, 36); p = s; //编译失败 在内含引用或const成员的class内支持赋值操作符,你必须自己定义copy assignm操作符。 如果某个base class将copy assignment操作符声明为private,编译器拒绝为其derived classes生成一个copy assignment操作符。 条款06: 若不想使用编译器自动生成的函数,就该明确拒绝。 方案:将相应成员函数声明为private并不予实现。或使用Uncopyable这样的base class 明确声明一个成员函数,阻止编译器暗自生成,另其为private,阻止外部调用。 但成员函数和friend仍然可以反问,所以只声明而不定义它们,如不慎调用,则会得到一个连接错误。 将连接期错误移到编译器: class Uncopyable{ protected: Uncopyable(){} //允许derived对象构造和析构 ~Uncopyablel(){} private: Uncopyable(const Uncopyable&); //但阻止copying Uncopyable& operator=(const Uncopyable&); }; class HomeForSale:private Uncopyable { …}; 当尝试拷贝HomeForSale对象时,自动生成copy构造函数和copy assignment操作符,并尝试调用基类对于版本,但失败。。。 条款07: 为多态基类声明virtual析构函数
1.若果class带有任何virtual函数,它就应该拥有一个virtual析构函数
2.class的设计目的不是作为base class使用或不是为了具备多态性,就不改声明为virtual析构函数
C++明确指出当derived对象经过base class指针被删除,而该base class带有一个non-virtual析构函数,则其结果未定义的:局部销毁。
每一个带有virtual函数的class都有一个相应的由函数指针构成的数组vtbl(virtual table). 当对象调用某一virtual函数,实际被调用的函数取决于该对象的vptr (virtual table pointer)所指的那个vtbl,编译器在其中寻找适当的函数指针。无端声明析构函数为virtual会增加为vptr的付出的空间补偿。
class SpecialString: public std::string{ //馊主意!
};
SpecialString *pss = new SpecialString(“aaa”);
std::string *ps=pss;
delete ps;//未定义!
如果企图继承一标准容器或任何其他带有non-virtual析构函数的class,拒绝诱惑吧!!!
为你希望成为抽象(不能实体化)的那个class声明一个pure virtual析构函数
class AWOV {
public: virtual ~AWOV() = 0;
};
必须提供一份定义 AWOV::AWOV() {}
不然派生类的析构函数调用base class的析构函数时,不然连接器会报错。
条款08:别让异常逃离析构函数
1.析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能突出异常,析构函数应该捕捉任何异常,然后吞下(不传播)或结束程序。
2.如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非析构函数中)执行该操作。
栈展开期间,释放局部对象所用的内存并运行类类型局部对象的析构函数。在执行析构函数前如果已经有一个异常,析构函数中又抛出一个异常,将会导致调用标准库 terminate 函数强制从程序非正常退出。
class DBConnection {
public : static DBConnection create();
void close();
};
确保DBConnection不忘记调用close()
class DBConn {
public:
~DBConn(){
db.close();}
private: DBConnection db;
};
DBConn dbc(DBConnection::create());
如果析构函数抛出异常:
1) 强迫结束
DBConn::~DBConn(){
try { db.close();}
catch(…) {
制作运转记录,记下对close的调用失败
std::abort();
}
2)吞下异常
DBConn::~DBConn(){
try { db.close();}
catch(…) {
制作运转记录,记下对close的调用失败
}
3)较佳的策略,客户自己调用普通函数
class DBConn {
public:
void close(){
db.close();
closed = true;
}
~DBConn(){
if(!closed) {
try { db.close(); }
catch(…){
制作运转记录,记下对close的调用失败
abort或吞下异常
}
}
private: DBConnection db;
bool closed;
};
条款09:绝不在构造和析构函数中调用virtual函数
因为这类调用从不下降至derived class (比起当前执行构造函数和析构函数的那层)
class Transaciton {
public:Transaciton();
virtual void logTransaciton() const = 0;
};
Transaciton::Transaciton(){
logTransaciton();
}
class BuyTransaciton:public Transaciton{
public:
virtual void logTransaciton() const;
};
class SellTransaciton:public Transaciton{
public:
virtual void logTransaciton() const;
};
BuyTransaciton b; //基类构造函数调用的是Transaciton::logTransaciton();
BuyTransaciton专属成分还未初始化,最安全的做法就是视而不见,当成Transaciton类。
对象在derived class 构造函数开始执行前不会成为一个derived class。
析构函数类似,在进入base class 析构函数后对象就成为一个base class对象。
解决方案:令derived class 将必要的构造信息上传至base class构造函数,来替换无法使用virtul函数从base class向下调用。
class Transaciton {
public:
explicit Transaciton(const string &loginfo);
void logTransaciton(const string &loginfo) const;
};
Transaciton::Transaciton(const string &loginfo){
logTransaciton(loginfo);
}
class BuyTransaciton:public Transaciton{
public:
BuyTransaciton( parameters): Transaciton( createLogString(parameters)) {…}
private:
static string createLogString(parameters);
};
条款10: 令operator= 返回一个reference to *this
条款11: 在operator= 中处理自我赋值
class Bitmap {…};
class Widget {
private: Bitmap *pb;
};
Widget& Widget::operator=(const Widget &rhs) {
delete pb; //没有进行自我赋值检测
pb = new Bitmap(*rhs.pb);//不具备异常安全,发生异常后,pb指向一块已删除的内存
return *this;
}
解决:证同测试
Widget& Widget::operator=(const Widget &rhs) {//不具备异常安全
if (this == &rhs) return *this;
delete pb;
pb = new Bitmap(*rhs.pb);//不具备异常安全
return *this;
}
Widget& Widget::operator=(const Widget &rhs) {
Bitmap *pOrig = pb; //异常安全,又能处理自我赋值,当自我赋值发生频率较高时,可加入证同测试。
pb = new Bitmap(*rhs.pb);
delete pOrig;
return *this;
}
另一替换方案确保异常安全,又能处理自我赋值:copy and swap技术
class Widget{
void swap(Widget &rhs);
};
Widget& Widget::operator=(const Widget &rhs){
Widget temp(rhs);
swap(temp);
return *this;
}
Widget& Widget::operator=(const Widget rhs){ //另一较高效写法,by-value
swap(rhs);
return *this;
}
条款12: 复制对象时勿忘其每一个成分
1.copying 函数应该确保赋值”对象内所有成员变量”及“所有base class成分”
2.不要尝试以某个copying函数实现另一个copying函数。应该将共同机能放进第三个函数中,并由两个函数共同调用。
为derived class编写copying函数时,必须复制其base class成分。
class Customer {
public:
Customer(const Customer &rhs);
Customer& operator=(const Customer &rhs);
private: string name;
};
class PriorityCustomer: public Customer {
public:
priorityCustomer(const priorityCustomer &rhs);
priorityCustomer& operator=(const priorityCustomer &rhs);
private: int priority;
};
priorityCustomer::priorityCustomer(const priorityCustomer &rhs)
:Customer(rhs), //调用base class的copy构造函数,如省略,则调用默认的构造函数,基类部分为默认值
priority(rhs.priority)
{ }
priorityCustomer& priorityCustomer::operator=(const priorityCustomer &rhs) {
Customer::operator=(rhs); //可先进行证同测试,成员如有指针
priority = rhs.priority;
return *this;
}
相关文章推荐
- effective C++ 3rd 笔记(二)
- effective C++ 3rd 笔记(三)
- Effective C++ 笔记(1):视C++为语言联邦
- Effective C++----3rd Edition, Item 1:将C++视为语言的联合体
- Effective C++ 读后感笔记
- Effective C++ 02 尽量以const, enum, inline替换#define 笔记
- Effective C++ 笔记
- Realtime Rendering 3rd笔记 3
- Effective C++_Item6笔记
- Effective C++ 05 了解C++默默编写并调用哪些函数 笔记
- Effective C++ (E3 41)笔记之了解隐式接口和编译器多态
- Effective C++ 笔记一 让自己习惯C++
- Effective C++学习笔记:条款2:尽量用iostram而不用stdio.h
- Effective C++ 学习笔记:为含指针变量的类声明一个拷贝构造函数和一个赋值操作符
- Effective C++ 学习笔记:让operator=返回*this的引用
- 《Effective C++》 笔记
- effective c++ 笔记 (30-31)
- Effective C++ 阅读笔记
- Effective c++学习笔记——条款10:令operator=返回一个*this的引用
- Effective C++_笔记_条款03_尽可能使用const