文章目录
1. 系统提供构造函数规则
- 系统默认会提供默认无参构造函数、拷贝构造(浅拷贝)和析构函数
- 如果用户定义拷贝构造函数,c++不会再提供任何默认构造函数
- 如果用户定义了普通有参构造函数(非拷贝),C++不在提供默认无参构造,但是会提供默认拷贝构造(浅拷贝)
1.1 代码示例
Person() //< 默认构造,自定义后,则编译器不再提供默认构造函数
{
std::cout << "enter into default constructor\n";
this->m_nAge = 0;
}
Person(const Person&per) //< 拷贝构造函数,自定义后,编译器则不再提供
{
std::cout << "enter into copy constructor\n";
this->m_nAge = per.m_nAge;
}
~Person()
{
std::cout << "enter into destructor\n";
}
private:
int m_nAge;
};
2. 默认构造函数
- 定义时的歧义:
class Person
{
public:
Person() //< 默认构造,自定义后,则系统不再提供默认构造函数
{
this->m_nAge = 0;
}
Person(int nAge = 10) //< 带默认参数会和默认构造产生歧义
{
this->m_nAge = nAge;
}
private:
int m_nAge;
};
int main()
{
Person p; //< 报错:使用哪个默认构造,会有歧义
return 0;
}
3. 拷贝构造函数
3.1 深拷贝和浅拷贝
- 如果类成员变量中存在指向堆空间的数据,那浅拷贝会导致重复释放内存的异常(解决上述问题就需要深拷贝)
3.2 调用拷贝构造的时机
Person() //< 默认构造,自定义后,则编译器不再提供默认构造函数
{
std::cout << "enter into default constructor\n";
this->m_nAge = 0;
}
Person(const Person&per) //< 拷贝构造函数,自定义后,编译器则不再提供
{
std::cout << "enter into copy constructor\n";
this->m_nAge = per.m_nAge;
}
~Person()
{
std::cout << "enter into destructor\n";
}
private:
int m_nAge;
};
void CallPer(Person per)
{
std::cout << "enter into CallPer function\n";
}
Person ReturnPer(Person &per)
{
return per;
}
int main()
{
//! 1.用对象来初始化新建对象,会调用拷贝构造函数
//Person p1;
//Person p2 = p1;
//! 2.对象以值传递的方式传给函数参数,会调用拷贝构造函数
//Person p3;
//CallPer(p3); //< 函数参数会有1个临时变量来接受传递的对象 Person per = p3,则再次调用拷贝构造
//! 3.函数以值传递的方式返回对象
Person p4,p5;
p5 = ReturnPer(p4); //< 返回p4的引用per,用p5 = per 会调用拷贝构造函数
return 0;
}
4. 带参构造函数
4.1 带单个参数
//! explicit关键字:适用于单参的构造函数,只能显示的调用构造函数,不能隐式的调用
class Person
{
public:
Person()
{
this->m_nAge = 0;
}
Person(int nAge) //< 带单个参数的有参构造,不加关键字explicit,则容易发生隐士转换;如果加了explicit关键字,隐士转换会报错
{
this->m_nAge = nAge;
}
private:
int m_nAge;
};
int main()
{
Person p = 10; //< 隐士转换:将int类型转换成Person类型;等同于Person p = Person(10);Person(10)即隐式的调用了有参构造Person(int nAge)
return 0;
}
5. 不使用拷贝构造和拷贝赋值运算符
5.1 方法一
将他们都声明为私有,不需要实现
缺点:万一有成员函数或者friend函数不小心调用它们,报错会推迟到链接的时候才会报错。通常报错越早越好
class CPerson
{
public:
CPerson(){}
~CPerson(){}
void Display(const CPerson& p)
{
*this = p; //< 成员函数调用拷贝赋值运算符(链接的时候才报错)
}
private:
CPerson(const CPerson&);
CPerson& operator=(const CPerson&);
};
void Test01()
{
CPerson p1;
CPerson p2;
//p1.Display(p2);
}
5.2 方法二
为当前类设置一个专门为了阻止拷贝动作而设计的基类
注:当有成员函数或friend函数尝试拷贝当前类对象,编译器会试着生成一个copy构造函数和一个copy assignment操作符,这些函数的“编译器生成版”会尝试调用其基类的对应星弟,那些调动会被编译器拒绝,因为其基类的拷贝函数是private
class CUncopyable
{
protected: //< 允许derived对象构造和析构
CUncopyable(){}
~CUncopyable(){}
private:
CUncopyable(const CUncopyable&); //< 组织copying
CUncopyable& operator=(const CUncopyable&);
};
class CAnimal: private CUncopyable
{
public:
CAnimal()
:m_a(0)
{
}
void Display(const CAnimal& p)
{
//*this = p; //< 成员函数调用拷贝赋值运算符(编译的时候就报错)
cout << m_a << endl;
}
private:
int m_a;
};
参考effective c++ 第三版 条款06 若不想使用编译器自动生成的函数,就该明确拒绝
6. 析构
6.1 别让异常逃离析构
- 析构函数绝对不要吐出异常。如果一个被析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后吞下它们(不传播)或结束程序(std::abort())。
- 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。
参考:effective c++ 第三版 条款08:别让异常逃离析构函数