构造和析构
c++构造函数私有化的作用
将一个类的构造函数私有化,可以使该类不能被实例化,同时不能被继承。但是在类内可以访问自身的private字段,所以我们可以通过static函数创建class对象,以引用或者指针的形式返回。这样就可以使用这个类的对象了
构造函数私有化的类的设计保证了其他类不能从这个类派生或者创建类的实例。
此外,还有这样的用途:例如,实现这样一个class,它在内存中至多存在一个,或者指定数量个的对象(可以在class的私有域中添加一个static类型的计数器,它的初值置为0,然后在GetInstance()中作些限制,每次调用它时先检查计数器的值是否已经达到对象个数的上限值,如果是则产生错误,否则才new出新的对象,同时将计数器的值增1)。最后,为了避免值复制时产生新的对象副本,除了将构造函数置为私有外,复制构造函数也要特别声明并置为私有。如果将构造函数设计成Protected,也可以实现同样的目的,但是可以被继承。
class OnlyHeapClass {
public:
static OnlyHeapClass* GetInstance()
{
cout << "create object,return a pointer" << endl;
return (new OnlyHeapClass);
}
void Destroy()
{
cout << "delete object" << endl;
delete this;
}
private:
OnlyHeapClass() {}
~OnlyHeapClass() {}
};
void main()
{
OnlyHeapClass *p = OnlyHeapClass::GetInstance();//静态成员函数的访问
p->Destroy();
return;
}
delete操作会调用析构函数,所以不能编译通过。提供一个Destroy成员函数,完成delete操作。在成员函数中,析构函数是可以访问的,当然detele操作也是可以编译通过。
深拷贝和浅拷贝
浅拷贝(编译器默认的时浅拷贝)只复制某个对象的指针不复制对象本身,新旧对象中的指针变量共享同一块内存,如果改变其中一个对象的指针变量,另一个对象的指针变量也会受到影响。在释放内存的时候会导致同一块区域被释放两次
深拷贝会创造另一个和当前对象完全一样的对象,但是新对象和原对象不共享内存,一个对象中指针变量的修改不会影响到另一个对象中的指针变量
- 在对象中有指针类型的成员变量时就使用深拷贝,否则使用浅拷贝
赋值构造和移动构造
赋值构造
当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值构造函数。当没有重载赋值构造函数(赋值运算符)时,如果没有定义赋值构造函数,编译器会自动定义合成的赋值构造函数
赋值运算符的重载声明如下:
A& operator = (const A& other)
拷贝构造和赋值构造很容易造成混淆
- 拷贝构造时一个对象初始化另一个对象,产生了一个新对象
- 赋值构造是一个对象赋值给另一个对象,两个对象都是原来存在的
参数为什么加const?
- 我们不希望在这个函数中对用来进行赋值的“原版”做任何修改。
- 加上const,对于const的和非const的实参,函数都能接受;如果不加,就只能接受非const的实参。
为什么使用引用?
- 这样可以避免在函数调用时对实参的一次拷贝,提高了效率。
**注意:**上面的规定都不是强制的,可以不加const,也可以没有引用。
返回值
一般地,返回值是被赋值者的引用,即*this(如上面例1),原因是:
- 这样在函数返回时避免一次拷贝,提高了效率。
- 更重要的,这样可以实现连续赋值,即类似a=b=c这样。如果不是返回引用而是返回值类型,那么,执行a=b时,调用赋值运算符重载函数,在函数返回时,由于返回的是值类型,所以要对return后边的“东西”进行一次拷贝,得到一个未命名的副本(有些资料上称之为“匿名对象”),然后将这个副本返回,而这个副本是右值,所以,执行a=b后,得到的是一个右值,再执行=c就会出错。
**注意:**这也不是强制的,我们甚至可以将函数返回值声明为void,然后什么也不返回,只不过这样就不能够连续赋值了
移动构造
使用浅拷贝的时候如果类中有指针,浅拷贝就会出现问题、使用深拷贝每次都是拷贝类中所有数据某种情况下这种方法可能消耗内存过大。因此就有了移动构造函数。
通过移动语义去实现构造函数,对于程序执行过程中产生的临时对象,往往只用于传递数据(没有其它的用处),并且会很快会被销毁。因此在使用临时对象初始化新对象时,我们可以将其包含的指针成员指向的内存资源直接移给新对象所有,无需再新拷贝一份,这大大提高了初始化的执行效率。
#include <iostream>
using namespace std;
class demo{
public:
demo():num(new int(0)){
cout<<"construct!"<<endl;
}
demo(const demo &d):num(new int(*d.num)){
cout<<"copy construct!"<<endl;
}
//添加移动构造函数
demo(demo &&d):num(d.num){
d.num = NULL;
cout<<"move construct!"<<endl;
}
~demo(){
cout<<"class destruct!"<<endl;
}
private:
int *num;
};
demo get_demo(){
return demo();
}
int main(){
demo a = get_demo();
return 0;
}
当类中同时包含拷贝构造函数和移动构造函数时,如果使用临时对象初始化当前类的对象,编译器会优先调用移动构造函数来完成此操作。只有当类中没有合适的移动构造函数时,编译器才会退而求其次,调用拷贝构造函数。
在实际开发中,通常在类中自定义移动构造函数的同时,会再为其自定义一个适当的拷贝构造函数,由此当用户利用右值初始化类对象时,会调用移动构造函数;使用左值(非右值)初始化类对象时,会调用拷贝构造函数。
如果使用左值初始化同类对象,但也想调用移动构造函数完成,有没有办法可以实现呢?
默认情况下,左值初始化同类对象只能通过拷贝构造函数完成,如果想调用移动构造函数,则必须使用右值进行初始化。C++11 标准中为了满足用户使用左值初始化同类对象时也通过移动构造函数完成的需求,新引入了 std::move() 函数,它可以将左值强制转换成对应的右值,由此便可以使用移动构造函数。
基类和子类构造析构执行顺序
构造函数: 先基类 后子类 子类构造函数可能需要使用基类元素
析构函数: 先子类 后基类
c++中为什么基类析构函数要使用virtual虚析构函数?
C++中基类采用virtual虚析构函数是为了防止内存泄漏。具体地说,如果派生类中申请了内存空间,并在其析构函数中对这些内存空间进行释放。假设基类中采用的是非虚析构函数,当删除基类指针指向的派生类对象时就不会触发动态绑定,因而只会调用基类的析构函数,而不会调用派生类的析构函数。那么在这种情况下,派生类中申请的空间就得不到释放从而产生内存泄漏。所以,为了防止这种情况的发生,C++中基类的析构函数应采用virtual虚析构函数。
参考
C++ 私有构造函数的作用_koudaidai的博客-CSDN博客