设计一个类不能被拷贝
拷贝只会用在两个场景中:拷贝构造函数以及赋值运算符重。因此想要让一个类禁止拷贝,
只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。我们可以用两种方式,一种是C++98,另外一种是C++11。具体实现如下。
class A
{
public:
A()
{}
//C++11
A(const A& a)=delete;
A& operator=(const A& a)=delete;
private:
//C++98
A(const A& a);
A& operator=(const A& a);
};
1. 在C++98中我们采取了把拷贝构造和赋值重载函数用只声明不定义的方式,并且把它的域作用限定符设为private,这样在类外就无法访问。
2. 在C++11中由于扩展了delete的用法,我们在默认成员函数后面加上=delete,表示让编译器删除该默认成员函数。
请设计一个类,只能在堆上创建对象
第一步我们当然是将构造函数设为私有的,但是不能遗漏了这两种场景,例如“ hp2=hp1 ”和“ hp3(hp1) ”,于是我们应该将拷贝构造函数和赋值重载函数(准确来说赋值重载可能不需要设置为私有,因为它是将一个对象的值赋值给另外一个已经存在的对象)也要设为私有的。
第二步我们要在类中写一个函数名为CreatObj,使得CreatObj函数能够在堆上创建出对象。而调用类中的这个函数需要实例化出来的对象,但是这个函数的功能又是用来创建对象的。这时就产生了先有鸡还是先有蛋的问题。不过我们在函数名前加上了static很好的规避了这个问题,调用这个CreatObj函数我们可以直接使用类域+域作用限定符的方式,例如:HeapOnly* hp3 = HeapOnly::CreatObj()。
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
private:
HeapOnly()
{
//...
}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly& hp) = delete;
};
请设计一个类,只能在栈上创建对象
方案一
与前面的类似,不过有几点不同。先来看下方案一的实现代码。
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{
//...
}
};
这个方案就无法规避外部进行拷贝构造。
StackOnly s1=StackOnly::CreatObj();
static StackOnly s2(s1); //在静态区拷贝构造对象
StackOnly* ps3=new StackOnly(s1); //在堆上拷贝构造对象(new 为 operator new + 构造)
我们不能将拷贝构造函数设为私有的,也不能用delete进行删除,因为CreatObj函数创建的是局部对象,返回时必然要调用拷贝构造函数。
方案二
由上面方法一知,new在堆上申请空间实际分为两步,第一步是调用operator new函数申请空间,第二步是在申请的空间上执行构造函数(或者拷贝构造函数),完成对象的初始化工作。我们想要禁止在堆上创建对象,既然不能将拷贝构造函数设为私有的,那么不妨将operator new设为私有或者直接用delete删除,这样就无法用new在堆上创建对象。
class StackOnly
{
public:
static StackOnly CreateObj()
{
StackOnly st;
return st;
}
private:
StackOnly()
{
//...
}
// 对一个类实现专属operator new
void* operator new(size_t size) = delete;
};
但还是没有办法防止在静态区拷贝构造对象。
请设计一个类,不能被继承
方案一:C++98
将该类的构造函数设置为私有即可,因为子类的构造函数被调用时,必须调用父类的构造函数初始化父类的那一部分成员,但是由于父类的构造函数设为了私有,在子类当中是不可见的,所以在创建子类对象时子类无法调用父类的构造函数对父类的成员进行初始化,因此该类被继承后子类无法创建出对象。
class NonInherit
{
public:
static NonInherit CreateObj()
{
return NonInherit();
}
private:
//将构造函数设置为私有
NonInherit()
{}
};
方案二:C++11
C++98的这种方式其实不够彻底,因为这个类仍然可以被继承(编译器不会报错),只不过被继承后无法实例化出对象而已。于是C++11中提供了final关键字,被final修饰的类叫做最终类,最终类无法被继承,此时就算继承后没有创建对象也会编译出错。
class NonInherit final
{
//...
};
请设计一个类,只能创建一个对象(单例模式)
单例模式的简介
- 单例模式是一种设计模式(Design Pattern),设计模式就是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式的目的就是为了可重用代码、让代码更容易被他人理解、保证代码可靠性程序的重用性。
- 单例模式指的就是一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
- 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象同一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
实现方式
饿汉模式
实现方式分为以下三步
- 将构造函数设为私有,将拷贝构造函数和赋值重载函数设为私有或者用delete删除,防止外部创建或者拷贝对象。 注意:为了防止在接口函数GetInstance中创建局部对象,以至于返回的时候需要用到拷贝构造函数。我们在Singleton类中声明静态成员_sinst,由于它是在静态区的,函数GetInstance直接返回_sinst即可。
- 提供获取单例对象的接口函数。
- 声明完成之后我们还要再类外完成单例对象的初始化。
namespace hungry
{
class Singleton
{
public:
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()
{
return _sinst;
}
private:
// 1、构造函数私有
Singleton()
{
// ...
}
// 3、防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
static Singleton _sinst;
};
//在类外完成对单例对象的初始化
//静态成员声明与定义分离
Singleton Singleton::_sinst;
}
缺点:饿汉模式一开始(main函数之前)就创建单例对象。
1、如果单例对象初始化内容很多,影响启动速度。
2、如果两个单例类,互相有依赖关系,无法确定哪个先初始化。
假设有A B两个单例类,要求A先创建,B再创建,B的初始化创建依赖A
懒汉模式
懒汉模式下不能像饿汉模式下一样在main函数之前创建对象,他是在第一次调用GetInstance函数时创建对象。
实现方式分为以下三步
- 将构造函数设为私有,将拷贝构造函数和赋值重载函数设为私有或者用delete删除,防止外部创建或者拷贝对象。
- 提供获取单例对象的接口函数。
- 提供一个指向单例对象的static指针,并在类外将其定义初始化为空。
namespace lazy
{
class Singleton
{
public:
// 2、提供获取单例对象的接口函数
static Singleton& GetInstance()
{
if (_psinst == nullptr)
{
// 第一次调用GetInstance的时候创建单例对象
_psinst = new Singleton;
}
return *_psinst;
}
// 一般单例不用释放。
// 特殊场景:1、中途需要显示释放 2、程序结束时,需要做一些特殊动作(如持久化)
static void DelInstance()
{
if (_psinst)
{
delete _psinst;
_psinst = nullptr;
}
}
class GC
{
public:
~GC()
{
lazy::Singleton::DelInstance();
}
};
private:
// 1、构造函数私有
Singleton()
{
// ...
}
~Singleton()
{
cout << "~Singleton()" << endl;
}
// 3、防拷贝
Singleton(const Singleton& s) = delete;
Singleton& operator=(const Singleton& s) = delete;
static Singleton* _psinst;
static GC _gc;
};
Singleton* Singleton::_psinst = nullptr;
Singleton::GC Singleton::_gc;
}
一般单例不用释放,但在特殊场景下我们需要释放。如果需要释放的话也可以实现,方案如下:
- 首先实现一个静态成员函数DelInstance完成对对象指针的释放工作。
- 在单例类中实现一个内嵌的垃圾回收类GC,然后在垃圾回收类的析构函数中调用Dellnstance完成单例对象的释放,这时它就可以同时完成显示释放和持久化。在单例类中定义一个静态的垃圾回收类对象_gc,并在类外进行定义,当该对象被消耗时就会调用其析构函数,这时便对单例对象进行了释放。