C++特殊类设计
C++很多场景下会需要一些特殊要求的类,比如不能拷贝、只能在栈上实例化对象这些。来看看怎么实现吧。
不能被拷贝
拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。
C++98和C++11有着不同的实现方式。
C++98,需要将拷贝和赋值私有化:
class CopyBan
{
public:
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
};

error C2248: “CopyBan::CopyBan”: 无法访问 private 成员(在“CopyBan”类中声明)
C++11则比较简单,只需要禁止编译器自动生成拷贝和赋值即可:
class CopyBan
{
public:
CopyBan() = default;
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
private:
};

error C2280: “CopyBan::CopyBan(const CopyBan &)”: 尝试引用已删除的函数
只能在堆上创建对象
1.首先我们要将构造函数私有化,然后提供一个静态成员函数来完成对象创建:
class HeapOnly
{
public:
static HeapOnly* CreatObject()
{
return new HeapOnly;
}
private:
HeapOnly()=default;
};
当然这是不够的,因为还可以通过下面方式创建对象:
int main()
{
HeapOnly ho1 = *HeapOnly::CreatObject();
HeapOnly ho2(*HeapOnly::CreatObject());
return 0;
}
2.因此我们还需要将拷贝和赋值给私有化或者禁掉
class HeapOnly
{
public:
static HeapOnly* CreatObject()
{
return new HeapOnly;
}
private:
HeapOnly() = default;
HeapOnly(const HeapOnly&) = default;
HeapOnly& operator=(const HeapOnly&) = default;
};
只能在栈上创建对象
同上一样的方法:
class StackOnly
{
public:
static StackOnly CreatObject()
{
return StackOnly();
}
private:
StackOnly() = default;
StackOnly(const StackOnly&) = default;
StackOnly& operator=(const StackOnly&) = default;
};
当然,如果你想要允许拷贝和赋值,可以改为禁掉new和delete:
class StackOnly
{
public:
static StackOnly CreatObject()
{
return StackOnly();
}
private:
StackOnly() = default;
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
};
也是一样的效果。
不能被继承
这个还是有两个版本。C++98上是将构造函数私有化,派生类调不动基类构造函数,无法继承:
class NonInherit
{
public:
static NonInherit GetInstance()
{
return NonInherit();
}
private:
NonInherit()
{}
};
C++11在类的继承里也讲过,只需要加上final关键即可:
class A final
{
// ....
};
单例模式
设计模式:
设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
像之前写过的适配器模式、迭代器模式之类的。
单例模式:
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
单例模式的两种实现方式:
- 饿汉模式
在程序启动前先实例化一个对象:
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
return &_sInfo;
}
string GetIp()
{
return _ip;
}
void SetIp(const string& ip)
{
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
string _ip = "127.0.0.1";
int _port = 80;
//...
// 声明
static ConfigInfo _sInfo;
};
// 定义
ConfigInfo ConfigInfo::_sInfo;
类似如此,把构造函数、拷贝构造和赋值重载通通私有化。然后在成员变量里放唯一的静态成员变量。
要注意我们的静态成员变量规则,在类内部的静态成员变量只是声明而不是定义,因此还需要在全局实例化成员变量。
这种实现方式优点自然是简单轻便,缺点是
1.很多单例类,都是饿汉模式,有些单例对象初始化资源很多,导致程序启动慢,迟迟进不了main函数
2.如果两个单例类有初始化依赖关系,饿汉也无法解决。因为我们无法控制哪个单例先实例化。
- 懒汉模式
第一次使用实例对象时,创建对象。
实现方式非常简单:
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
static ConfigInfo info;
return &info;
}
string GetIp()
{
return _ip;
}
void SetIp(const string& ip)
{
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
string _ip = "127.0.0.1";
int _port = 80;
//...
};
在GetInstance上以static方式初始化变量并返回,需要注意这种方式只能在C++11以后的版本使用。
在C++11之前的static初始化不保证线程安全,意思是可能导致内存泄漏
所以在C++11以前的版本要通过锁和检查的方式完成初始化:
class ConfigInfo
{
public:
static ConfigInfo* GetInstance()
{
unique_lock<mutex> lock(_mtx);
if (_spInfo == nullptr) // 线程安全
{
_spInfo = new ConfigInfo;
}
return _spInfo;
}
string GetIp()
{
return _ip;
}
void SetIp(const string& ip)
{
_ip = ip;
}
private:
ConfigInfo()
{
cout << "ConfigInfo()" << endl;
}
ConfigInfo(const ConfigInfo&) = delete;
ConfigInfo& operator=(const ConfigInfo&) = delete;
private:
string _ip = "127.0.0.1";
int _port = 80;
//...
static ConfigInfo* _spInfo;
static mutex _mtx;
};
ConfigInfo* ConfigInfo::_spInfo = nullptr;
mutex ConfigInfo::_mtx;
当然我们发现ConfigInfo函数里的锁只是用来防止第一次初始化,但是后面每次调用都要加锁是十分影响效率的。因此考虑双检查法加锁来增加效率:
static ConfigInfo* GetInstance()
{
// 双检查加锁
if (_spInfo == nullptr) // 性能
{
unique_lock<mutex> lock(_mtx);
if (_spInfo == nullptr) // 线程安全
{
_spInfo = new ConfigInfo;
}
}
return _spInfo;
}
这样只有在第一次初始化的时候才会加锁,后面调用都不会加锁了。
更多推荐



所有评论(0)