文章目录
一、请设计一个类,只能在堆上创建对象
实现方式:
① 将构造函数私有,【C++98:将拷贝构造(只声明不实现)私有】or【C++11:删除拷贝构造】。
② 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。
C++98:将拷贝构造(只声明不实现)私有:
① 只声明不实现,是因为根本不会被调用,而且实现可能会很麻烦,不实现反而还简单。
② 私有,是因为如果只声明但没有设置成 private ,用户如果在类外定义了,就起不到禁止拷贝的作用了。
示例代码:
//请设计一个类,只能在堆上创建对象
class HeapOnly
{
public:
static HeapOnly* CreateObj()
{
return new HeapOnly;
}
HeapOnly(const HeapOnly& ho) = delete; // 防止调用拷贝构造在栈上创建对象
private:
HeapOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
//HeapOnly ho1; //编译报错
//HeapOnly* pho = new HeapOnly; //编译报错
HeapOnly* ptr1 = HeapOnly::CreateObj();
//HeapOnly copy(*ptr1); //编译报错
return 0;
}
二、请设计一个类,只能在栈上创建对象
实现方式:
① 将构造函数私有。
② 提供一个静态的成员函数,在该静态成员函数中完成栈对象的创建。
③【C++98:将 new 重载函数(只声明不实现)私有】or【C++11:删除 new 重载函数】。
示例代码:
//请设计一个类,只能在栈上创建对象
class StackOnly
{
public:
static StackOnly CreateObj()
{
return StackOnly();
}
void* operator new(size_t size) = delete; // 防止new调用拷贝构造申请对象在堆上
void operator delete(void* p) = delete;
private:
StackOnly()
:_a(0)
{}
private:
int _a;
};
int main()
{
//StackOnly* ptr2 = new StackOnly; //编译报错
//StackOnly so1; //编译报错
StackOnly so2 = StackOnly::CreateObj();
//StackOnly* ptr3 = new StackOnly(so2); //编译报错
return 0;
}
三、请设计一个类,不能被拷贝
实现方式:
①【C++98:将拷贝构造和赋值重载(只声明不实现)私有】or【C++11:删除拷贝构造和赋值重载】。
拷贝只会发生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
示例代码:
//请设计一个类,不能被拷贝
class CopyBanned
{
public:
CopyBanned()
:_a(0)
{}
CopyBanned(const CopyBanned& cb) = delete; // 防拷贝
CopyBanned& operator=(const CopyBanned& cb) = delete; // 防赋值
private:
int _a;
};
int main()
{
CopyBanned cb1;
//CopyBanned cb2(cb1); //编译报错
CopyBanned cb3;
//cb1 = cb3; //编译报错
return 0;
}
四、请设计一个类,不能被继承
实现方式:
①【C++98:将构造函数私有】or【C++11:加 final 关键字】。
C++98:将构造函数私有,派生类中调不到基类的构造函数,从而间接达到基类无法被继承的目的。
C++11:加 final 关键字,final 修饰类,表示该类不能被继承。
示例代码:
//请设计一个类,不能被继承
class NonInherit final
{
public:
NonInherit()
:_a(0)
{}
private:
int _a;
};
class B : public NonInherit //编译报错
{
};
五、请设计一个类,只能创建一个对象(单例模式)
-
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
使用设计模式的目的:为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。 -
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
单例模式有两种实现模式:饿汉模式和懒汉模式。
- 饿汉模式:提前准备好,在 main 函数之前就创建好了单例对象,程序随时可以访问这个单例对象。
- 懒汉模式:事先没有准备,第一次访问时,才创建单例对象。
1.饿汉模式
示例代码:
//饿汉模式
class CallInfo
{
public:
static CallInfo& GetInstance()
{
return _inst;
}
void AddCnt(int n)
{
_cnt += n;
}
int GetCnt()
{
return _cnt;
}
// 防拷贝
CallInfo(const CallInfo&) = delete;
CallInfo& operator=(const CallInfo&) = delete;
private:
CallInfo() // 构造函数私有
:_cnt(0)
{}
private:
int _cnt;
static CallInfo _inst; // 静态单例对象
};
// 类的静态成员变量在类外完成初始化
CallInfo CallInfo::_inst; // 在程序入口之前就完成单例对象的初始化
2.懒汉模式
示例代码:
//懒汉模式
class CallInfo
{
public:
static CallInfo& GetInstance()
{
// 注意这里一定要使用Double-Check的方式加锁,才能保证效率和线程安全
if (_pInst == nullptr)
{
std::unique_lock<mutex> lock(_mtx);
if (_pInst == nullptr)
{
_pInst = new CallInfo;
}
}
return *_pInst;
}
// 一般懒汉的单例对象,不需要自己回收,因为进程正常结束,资源都会还给系统
// 但是如果在单例对象释放析构时,有一些要完成的动作,比如要记录日志等等
// 那么可以考虑搞一个类似下面的回收类,帮助完成这个事情
// 实现一个内嵌垃圾回收类
class CGarbo
{
public:
~CGarbo()
{
if (_pInst)
{
delete _pInst;
}
}
};
// 定义一个静态成员变量,程序结束时,系统会自动调用它的析构函数从而释放单例对象
static CGarbo gc;
void AddCnt(int n)
{
_cnt += n;
}
int GetCnt()
{
return _cnt;
}
// 防拷贝
CallInfo(const CallInfo&) = delete;
CallInfo& operator=(const CallInfo&) = delete;
private:
CallInfo() // 构造函数私有
:_cnt(0)
{}
private:
int _cnt;
static mutex _mtx; // 互斥锁,用于保证创建单例对象的安全性
static CallInfo* _pInst; // 单例对象指针,用于获取单例对象
};
// 类的静态成员变量在类外完成初始化
CallInfo* CallInfo::_pInst = nullptr;
mutex CallInfo::_mtx;
CallInfo::CGarbo gc;
还有一种写法,但这种写法只能用于 C++11 及更高版本中。
示例代码:
//懒汉模式
class CallInfo
{
public:
static CallInfo& GetInstance()
{
// C++98 中多线程调用时,static sInst对象构造初始化并不能保证线程安全
// C++11 优化了这个问题,static sInst对象构造初始化是线程安全的
static CallInfo sInst;
return sInst;
}
void AddCnt(int n)
{
_cnt += n;
}
int GetCnt()
{
return _cnt;
}
CallInfo(const CallInfo&) = delete;
CallInfo& operator=(const CallInfo&) = delete;
private:
CallInfo()
:_cnt(0)
{}
private:
int _cnt;
};
3.总结
饿汉模式:
- 优点:简单。
- 缺点:① 无法控制单例创建初始化顺序。② 可能会导致进程启动慢。
懒汉模式:
- 优点:对应解决饿汉模式的两个缺点。
- 缺点:① 相对复杂一些,尤其是在创建单例对象时需要考虑线程安全问题。