目录
[一. 设计一个类,不能被拷贝](#一. 设计一个类,不能被拷贝)
[二. 设计一个类,使其只能在堆上创建](#二. 设计一个类,使其只能在堆上创建)
[2.1 方式一](#2.1 方式一)
[2.2 方式二](#2.2 方式二)
[2.3 方式三](#2.3 方式三)
[三. 设计一个类,使其只能在栈上创建](#三. 设计一个类,使其只能在栈上创建)
[四. 单例模式(设计一个类,只能创建一个对象))](#四. 单例模式(设计一个类,只能创建一个对象)))
[4.1 设计模式](#4.1 设计模式)
[4.2 单例模式](#4.2 单例模式)
[4.2.1 饿汉模式](#4.2.1 饿汉模式)
[4.2.2 懒汉模式](#4.2.2 懒汉模式)
一. 设计一个类,不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝, 只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
- C++98
在C++98中,我们需要将拷贝构造函数以及赋值运算符重载函数设为私有,并且只声明,不实现。
cpp
class CopyBan
{
// ...
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
//...
};
原因:
(1)设置成私有:如果只声明没有设置成private,用户自己如果在类外定义了,就可以不 能禁止拷贝了
(2)只声明不定义:不定义是因为该函数根本不会调用,定义了其实也没有什么意义,不写 反而还简单,而且如果定义了就不会防止成员函数内部拷贝了。
- C++11
C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上
=delete,表示让编译器删除掉该默认成员函数。
cpp
class CopyBan
{
// ...
CopyBan(const CopyBan&)=delete;
CopyBan& operator=(const CopyBan&)=delete;
//...
};
二. 设计一个类,使其只能在堆上创建
2.1 方式一
只能在堆上创建,那么我们就只能通过申请资源的方式创建对象。
首先我们要将构造函数私有化,拷贝构造以及赋值拷贝禁掉,来防止通过栈上的对象拷贝赋值或者直接在栈上创建对象来创建对象。在我们禁掉构造函数以及拷贝构造之后,可以不考虑赋值拷贝的问题,因为已经不可能通过直接构造或者拷贝来创建栈上对象了,而创建出的对象一定是CreateOnly函数创建的,即一定是堆上的,而赋值拷贝是针对已经有的对象,所以可以不考虑。
那我们就要通过自己实现的CreateOnly函数来调用构造函数创建对象,并且将此函数加上static修饰,因为我们将构造函数私有化之后,是不能创建出对象的,就不能通过对象去调用CreateOnly函数,因此要加上static,这样直接用类域调用就行。
cpp
//只能在堆上
class Heaponly
{
public:
static Heaponly* CreateOnly()//静态是因为不能通过构造函数构造对象,所以不能对函数进行引用
{
return new Heaponly;
}
Heaponly(const Heaponly& ho) = delete;
Heaponly& operator=(const Heaponly& ho) = delete;
private:
Heaponly()
{}
int _x;
int _y;
vector<int> _a;
};
int main()
{
//Heaponly h;
Heaponly* hp = Heaponly::CreateOnly();
return 0;
}
2.2 方式二
如果有多种构造函数,则可以用可变参数模板:
cpp
//只能在堆上
class Heaponly
{
public:
template<class... Args>
static Heaponly* CreateOnly(Args... args)//静态是因为不能通过构造函数构造对象,所以不能对函数进行引用
{
return new Heaponly(args...);
}
Heaponly(const Heaponly& ho) = delete;//防止通过栈上的对象拷贝构造
Heaponly& operator=(const Heaponly& ho) = delete;//防止通过栈上的对象赋值拷贝
private:
//构造私有化
Heaponly()
{}
Heaponly(int x,int y)
:_x(x)
,_y(y)
{}
int _x;
int _y;
vector<int> _a;
};
int main()
{
//Heaponly h;
//Heaponly* hp1 = new Heaponly;
//Heaponly* hp = Heaponly::CreateOnly();
Heaponly* hp1 = Heaponly::CreateOnly(1, 1);
Heaponly* hp2(hp1);
return 0;
}
2.3 方式三
我们还可以通过把析构函数私有化来实现只能在堆上创建。
这样只要是栈上创建的对象都会编译报错,因为无法调用其析构函数,但是指针却可以正常的开辟空间,那我们要如何释放空间呢,可以自己定义一个销毁函数来调用析构函数销毁对象。
cpp
//只能在堆上创建还可以封掉析构函数
class Heaponly
{
public:
Heaponly()
{}
Heaponly(int x,int y)
:_x(x)
,_y(y)
{}
void Destory()
{
delete this;
}
private:
~Heaponly()
{
cout << "~Heaponly()" << endl;
}
int _x;
int _y;
vector<int> _a;
};
int main()
{
//Heaponly ho1;
//Heaponly* ptr = new Heaponly;
//delete ptr;
//ptr->Destory();
shared_ptr<Heaponly> ptr(new Heaponly, [](Heaponly* ptr) {ptr->Destory(); });
return 0;
}
三. 设计一个类,使其只能在栈上创建
只在栈上创建对象,也就是要禁止申请资源空间。就需要变换一下CreateOnly函数,通过调用拷贝构造来构造对象。
并且还要禁掉new,在C++中,new是由operator new和构造函数组成的,那我们就得重载一个operator new函数,并且禁掉。
operator new
- 是C++中用于动态分配内存的内置运算符
- 主要作用是分配一块连续的内存空间,以便在其中存储对象或数据
- 可以被重载
cpp
//只能在栈上
class Stackonly
{
public:
template<class... Args>
static Stackonly CreateOnly(Args... args)//静态是因为不能通过构造函数构造对象,所以不能对函数进行引用
{
return Stackonly(args...);
}
Stackonly(const Stackonly& ho) = delete;
Stackonly& operator=(const Stackonly& ho) = delete;
//重载一个类专属的operator new
void* operator new(size_t n) = delete;
private:
//构造私有化
Stackonly()
{}
Stackonly(int x,int y)
:_x(x)
,_y(y)
{}
int _x;
int _y;
vector<int> _a;
};
int main()
{
Stackonly so1 = Stackonly::CreateOnly();
Stackonly so2 = Stackonly::CreateOnly(1, 1);
//如果是new的话就避免不了,就要封掉operator new
//Stackonly* so3 = new Stackonly(so1);
return 0;
}
也可以跟上面一样,把析构函数私有化,这样申请到的资源无法释放,也就不能在堆上创建对象。
那么类似,delete跟new一样,是由operator delete和析构函数组成的。那么我们也可以重载一个operator delete函数并且禁掉。
operator delete
- 和operator new配套的运算符,用于释放动态分配内存的内置运算符
- 通过传递要释放的内存块的指针,它将该内存块返回给系统或内存管理器,以便将其重新分配给其他用途
- 允许重载
cpp
void* operator delete(size_t n) = delete;
四. 单例模式(设计一个类,只能创建一个对象))
4.1 设计模式
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
设计模式目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化。
4.2 单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
4.2.1 饿汉模式
在饿汉模式中,单例实例是在main函数之前就被创建。
因为只能有一个对象,所以拷贝构造函数以及赋值拷贝函数都要禁用,构造函数需要私有。
并且要实现一个接口函数来返回一个对象,并且这个对象必须是static,因为返回的必须是全局的,而不是局部的。
接口函数也要设计为static的,因为构造函数私有,不能通过对象调用,只能用类域调用。
由于是预先实例化,所以在类中实例化就行。
cpp
namespace hunger
{
//饿汉:从一开始(main之前)就创建对象
//问题:
//1、如果单例对象数据较多,构造初始化成本较高,那么会影响程序启动的速度,迟迟进不了main函数
//2、多个单例类对象有初始化启动依赖关系,饿汉无法控制。假设:A和B两个单例,假设要求A先初始化,B后初始化,饿汉满足不了
class Singleton
{
public:
static Singleton* GetInstance()
{
return &_sint;
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << endl;
}
void Addstr(const string& s)
{
_vstr.push_back(s);
}
Singleton(const Singleton& s) = delete;
Singleton operator=(const Singleton& s) = delete;
private:
Singleton(int x=0,int y=0,const vector<string>& vstr={"yyyyy","xxxxx"})
:_x(x)
,_y(y)
,_vstr(vstr)
{}
//想让一些数据,当前程序只有一份,那就可以把这些数据放到这个类里面
//再把这个类设计为单例,这样数据就只有一份了
int _x;
int _y;
vector<string> _vstr;
//直接把这个对象定义在类中
//这样并不是嵌套定义,因为static对象并不在Singleton类中,而是在静态区,所以不是嵌套定义
static Singleton _sint;
};
Singleton Singleton::_sint(1, 1, { "陕西","四川" });
}
4.2.2 懒汉模式
由于饿汉模式存在问题,第一个就是单例对象数据较多,构造初始化成本较大,程序启动的速度就会很慢,迟迟进不了main函数,第二就是,如果对于多个单例类对象具有初始化依赖关系,饿汉模式无法满足,比如对于A和B两个单例类对象,如果要求A先创建,B后创建,饿汉模式无法满足。
cpp
//懒汉
//完美的解决了饿汉的问题
namespace lazy
{
class Singleton
{
public:
static Singleton* GetInstance()
{
if(_sint==nullptr)//在_sint为空时才对_sint进行创建
{
_sint = new Singleton;
}
return _sint;
}
static void DelInstance()
{
if (_sint)
{
delete _sint;
_sint = nullptr;
}
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << _sint << endl;
cout << endl;
}
void Addstr(const string& s)
{
_vstr.push_back(s);
}
Singleton(const Singleton& s) = delete;
Singleton operator=(const Singleton& s) = delete;
private:
Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxxxx","yyyyyy" })
:_x(x)
,_y(y)
,_vstr(vstr)
{}
~Singleton()
{
//把数据写到文件
cout << "~Singleton()" << endl;
}
int _x;
int _y;
vector<string> _vstr;
static Singleton* _sint;
};
Singleton* Singleton::_sint = nullptr;
}
懒汉模式推迟了实例化对象的创建,直到访问该实例化对象时才创建。
那么只需判断是不是第一次访问该对象,就可以知道该不该创建。
我们就可以用一个指针。如果该指针为空,就证明是第一次,就创建对象。要注意的是,该指针必须是动态开辟出来的,不然没法取地址。而且在析构时,由于这个指针是我们定义的,不能自动析构,所以要定义一个函数来专门销毁他。
这就是显示析构。
我们也可以让他自动调用来析构,就不用显示调用了。
并且如果想要在程序结束前,实现某些操作,就可能不太方便。所以我们可以定义一个类来实现程序结束后自动调用函数来销毁指针。
cpp
//懒汉
//完美的解决了饿汉的问题
namespace lazy
{
class Singleton
{
public:
static Singleton* GetInstance()
{
if(_sint==nullptr)//在_sint为空时才对_sint进行创建
{
_sint = new Singleton;
}
return _sint;
}
static void DelInstance()
{
if (_sint)
{
delete _sint;
_sint = nullptr;
}
}
void Print()
{
cout << _x << endl;
cout << _y << endl;
for (auto& e : _vstr)
{
cout << e << " ";
}
cout << _sint << endl;
cout << endl;
}
void Addstr(const string& s)
{
_vstr.push_back(s);
}
Singleton(const Singleton& s) = delete;
Singleton operator=(const Singleton& s) = delete;
private:
Singleton(int x = 0, int y = 0, vector<string> vstr = { "xxxxxx","yyyyyy" })
:_x(x)
,_y(y)
,_vstr(vstr)
{}
~Singleton()
{
//把数据写到文件
cout << "~Singleton()" << endl;
}
int _x;
int _y;
vector<string> _vstr;
static Singleton* _sint;
//内部类
class GC
{
public:
~GC()
{
Singleton::DelInstance();
}
};
static GC gc;
};
Singleton* Singleton::_sint = nullptr;
Singleton::GC Singleton::gc;
}
这样随着GC类的析构,指针就自动被销毁了。
总结:
好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。
祝大家越来越好,不用关注我(疯狂暗示)