注意点1:三个函数都是public且inline 且non-virtual.
注意点2:Copy 构造函数 和 CopyAssignment 操作符 会将源对象的每一个非non-static 拷贝的目标对象。
例子:
#include<iostream>
class CTestA
{
public:
void SetValue(int a,int *b)
{
i = a;
j = b;
}
void Show()
{
std::cout<<"i = "<<i<<std::endl;
std::cout<<"j = "<<j<<std::endl;
}
private:
int i;
int *j;
static int k;
};
int main()
{
CTestA a,b;//采用默认构造函数
std::cout<<"defalut a:"<<std::endl;
a.Show();
std::cout<<"defalut b:"<<std::endl;
b.Show();
int i = 0;
int *j = &i;
a.SetValue(i,j);
b = a;
std::cout<<"SetValue a:"<<std::endl;
a.Show();
std::cout<<"Copy assignmentb:"<<std::endl;
b.Show();
CTestA c(a);
std::cout<<"Copy c:"<<std::endl;
c.Show();
return 0;
}
结果:
条款6:如果不想使用系统默认的这三个默认函数,需要明确的拒绝。
拒绝的方法:将需要拒绝的函数放置到private区域。
如:
#include<iostream>
class CTestA
{
public:
void SetValue(int a,int *b)
{
i = a;
j = b;
}
void Show()
{
std::cout<<"i ="<<i<<std::endl;
std::cout<<"j ="<<j<<std::endl;
}
private:// 这里
CTestA(const CTestA &other);
CTestA & operator=(const CTestA&other);
private:
int i;
int *j;
static int k;
};
条款7:为多态基类声明virtual析构函数
注意点1:如果该类不会作为基类(也就是被其他继承)请不要使用virtual析构函数。另外,请不要继承使用non-virtual析构。
比如:classCTestA:publicstd::string
如果你使用的一个指针指向它,在析构的时候将会步入未定义行为的恶地。
技巧点:使用pure析构函数 (纯虚函数)
例:注意到即使使用pure析构函数同样可以再定义。
#include<iostream>
#include<string>
classCTestBase
{
public:
virtual ~CTestBase() = 0;
void SetValue(int a,int *b)
{
i = a;
j = b;
}
void Show()
{
std::cout<<"i ="<<i<<std::endl;
std::cout<<"j ="<<j<<std::endl;
}
private:
int i;
int *j;
};
CTestBase::~CTestBase()
{
std::cout<<"this is~Base"<<std::endl;
}
classCTestDerived:public CTestBase
{
public:
virtual ~CTestDerived();
};
CTestDerived::~CTestDerived()
{
std::cout<<"this is~Derived"<<std::endl;
}
int main()
{
CTestDerived a;
return 0;
}
条款8:严禁析构出现异常
注:此处不是谈论异常的处理,而是谈论如何在析构的时候避开异常提示。
此处列举的例子具有具体性,假设一个数据库类,在析构的时候需要断开此连接
例子:
1. 一般不会采用的做法:
classCTestDataBase
{
public:
CTestDataBase()
{
std::cout<<"ConnectDB!"<<std::endl;
}
~CTestDataBase()
{
std::cout<<"CloseDB!"<<std::endl;
}
};
2. 比较好的做法:
#include<stdexcept>
classCTestDataBase
{
public:
CTestDataBase()
{
ConnectDB();
}
~CTestDataBase()
{
if(!m_bClosed)// 双保险2
{
try
{
CloseDB();
}
catch(...)
{
std::cout<<"CloseDBerror"<<std::endl;
}
}
}
void ConnectDB()
{
std::cout<<"ConnectDB!"<<std::endl;
m_bClosed = false;
}
void CloseDB()//双保险1
{
std::cout<<"CloseDB!"<<std::endl;
m_bClosed = true;
}
private:
bool m_bClosed;
};
int main()
{
CTestDataBase a;
return 0;
}
条款9:绝不在构造函数和析构函数中调用virtual函数
注意点:逻辑本身不建议这么做。
例如:
#include <iostream>
#include <string>
#include <stdexcept>
class CBase
{
public:
CBase()
{
Log();
}
virtual void Log()
{
std::cout<<"Base Log"<<std::endl;
}
};
class CDerived:public CBase
{
public:
CDerived():CBase()
{
Log();
}
virtual void Log()
{
std::cout<<"Derived Log"<<std::endl;
}
};
int main()
{
CDerived a;
return 0;
}
执行结果:
如此运行了两次,如果非必要的话,请谨慎使用。
条款10:令operator= 返回一个reference to *this
注:此处的operator=泛指+=/ -= / *= / /= ..等等
原因:连续赋值。
#include <iostream>
#include <string>
#include <stdexcept>
class CTest
{
public:
void SetValue(int v)
{
val = v;
}
int GetValue() const
{
return val;
}
void ShowValue()
{
std::cout<<"val = "<<val<<std::endl;
}
CTest& operator=(const /*小心这里的const 只能调用const函数*/ CTest& other)
{
this->SetValue(other.GetValue());
return *this;
}
private:
int val;
};
int main()
{
CTest a,b,c;
a.SetValue(1);
b = c = a;
std::cout<<"a:";
a.ShowValue();
std::cout<<"b:";
b.ShowValue();
std::cout<<"c:";
c.ShowValue();
return 0;
}
条款11:小心在operator=中对自己赋值
处理办法1:对自我赋值进行检测
CTest& operator=(const CTest& other)
{
if(this == other)
return *this;
this->SetValue(other.GetValue());
return *this;
}
处理办法2(增加了对出现异常的处理):
注:两种书写方式本质上是一样的,swap不必再区别2个对象是否还会存在不同。
//书写方式1
CTest& operator=(const CTest& other)
{
CTest other2(other); // 多构建一个副本
swap(other2);
return *this;
}
//书写方式2
CTest& operator=(CTest other)
{
swap(other);
return *this;
}
条款12:复制对象时请不要忘记任何一个成分
注意点:
这是个有意思的注意点:不要在派生类中的copyassignment 调用 copy 构造函数。因为那样的结果是不确定的,除非你自己很确定。
后记:三个默认函数就像三剑客一般,一般你需要重新定义其中一个,你就需要另外两个。