目录
在日常的学习中,我们可能会遇到各种的特殊类的设计,所以本期的内容就是学习C++中重要的特殊类的设计。
特殊类1
情景:设计一个类,只能在堆上创建对象。
我们先简单分析一下,只能在堆上创建,意味着不能在类外调用构造函数和拷贝构造函数创建对象。所以我们就要把构造函数和拷贝构造函数设置为私有,且最终只能在类中调用new创建对象。基于此,我们来进行编码实现。
cpp
#include<iostream>
using namespace std;
class HeapOnly
{
public:
static HeapOnly* Create()
{
return new HeapOnly;
}
private:
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly()
:_a(10)
{}
private:
int _a;
};
int main()
{
HeapOnly* hop=HeapOnly::Create();
}
特殊类2
情景:设计一个类,只能在栈上创建对象。
我们先来简单分析一下,只能在栈上创建,就意味着不能使用new关键字创建对象,只需要把new关键字重载之后删除掉该函数即可。编码如下。
cpp
class StackOnly
{
public:
StackOnly()
:_a(0)
{}
void* operator new(size_t a) = delete;
void operator delete(void* p) = delete;
private:
int _a;
};
int main()
{
StackOnly* sop = new StackOnly;
return 0;
}
特殊类3
情景:设计一个类,不能被拷贝。
我们先来分析一下,不能被拷贝,意味着拷贝构造函数和赋值运算符重载不能被调用,所以直接将拷贝构造和赋值运算符重载delete即可。
cpp
class NoCopy
{
public:
NoCopy()
:_a(0)
{}
NoCopy(const NoCopy& nc) = delete;
NoCopy& operator=(const NoCopy& nc) = delete;
private:
int _a;
};
特殊类4
情景:设计一个类,该类不能被继承。
我们简单思考一下,C++98可能会设置成该类的构造函数为私有,因为在子类中,调用子类的构造函数是,子类继承父类的成员会调用父类的构造函数进行初始化,所以如果将父类的构造函数设置成了私有,就不能在子类的构造函数中去调用,进而报错,所以采取这样的方式可以使得父类不被继承,但是这种方式太过传统,我们使用C++11中final关键字,使得该父类为最终类,可以使得父类不被继承。编码如下。
cpp
class FinalClass final
{
};
class Children :public FinalClass
{
};
特殊类5
情景:设计一个类,该类只能创建一个对象。
在设计这个类之前,我们先来了解一下设计模式的概念。
设计模式其实就是一种设计理念,日常生活中每一个成熟的行业都有一个成熟的设计模式,大家可以通俗的理解为设计模式就是设计模板,可以根据先辈探索创建的经验形成了一个固有的套路,往后都根据套路进行设计,可以提高效率。
在C++中也有大量的设计模式,今天我们学习的主要是单例模式,我们可以使用单例模式创建一个只能创建一个对象的类。
单例模式
一个类只能创建一个对象,即单例模式,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。
懒汉模式单例模式
懒汉就意味着,需要单例对象时,才去访问对应的接口创建对象。编码如下。
cpp
class SingleTon
{
public:
static SingleTon& GetInstance()
{
if (_st == nullptr)
{
_st = new SingleTon;
}
return *_st;
}
int Get()
{
return _a;
}
SingleTon(const SingleTon& st) = delete;
SingleTon& operator=(const SingleTon& st) = delete;
private:
SingleTon()
:_a(0)
{}
private:
int _a;
static SingleTon* _st;
};
SingleTon* SingleTon::_st = nullptr;
int main()
{
SingleTon::GetInstance();
cout << SingleTon::GetInstance().Get() << endl;
cout << &SingleTon::GetInstance() << endl;
SingleTon::GetInstance();
cout << &SingleTon::GetInstance() << endl;
return 0;
}

最终确实只创建了一个对象。
那么我们创建的这个懒汉模式的单例模式代码有什么问题吗?如果有多个线程来共同访问GetInstance这个接口呢?
此时就会面临线程安全的问题,虽然静态的指针只有一个,但是当两个线程如果第一次都进入了if语句时,此时就会创建多个对象,静态的指针先后接受了多个对象的地址。所以为了防止线程安全的产生,需要进行加锁。
所以我们对GetInstance这个函数内部进行了加锁。
cpp
static SingleTon& GetInstance()
{
unique_lock<mutex> uq(_mtx);
if (_st == nullptr)
{
_st = new SingleTon;
}
return *_st;
}
但是这个代码还是有点问题的,就是当第一次单例对象创建完毕之后,之后每个线程来进行获取单例对象时,都是一个串行处理的过程,所以,为了提高效率,我们再对此代码进行改进。
cpp
static SingleTon& GetInstance()
{
if (_st == nullptr)
{
unique_lock<mutex> uq(_mtx);
if (_st == nullptr)
{
_st = new SingleTon;
}
}
return *_st;
}
我们采用了双if的方式,既保证了线程安全,又实现了多个线程的并行执行,提高了效率,这个方式是我们以后在使用锁和多线程编程中常用的一种方法。
饿汉模式单例模式
饿汉模式大家可以形象的理解为比较饿,所以一开始就创建了单例对象。编码如下。
cpp
class SingleTon
{
public:
static SingleTon& GetInstance()
{
return _st;
}
int Get()
{
return _a;
}
SingleTon(const SingleTon& st) = delete;
SingleTon& operator=(const SingleTon& st) = delete;
private:
SingleTon()
:_a(0)
{}
private:
int _a;
static SingleTon _st;
};
SingleTon SingleTon::_st;
饿汉模式和懒汉模式单例模式比较
懒汉模式:
- 需要单例对象时才进行创建(main函数内,程序启动之后)。可以控制单例对象创建的时机,但是会有线程安全的问题以及多线程访问时效率的问题需要我们手动进行控制。
饿汉模式
- 一开始就创建了单例对象(main函数之前,程序启动前)。不能控制单例对象的创建时机,且如果刚开始创建单例的时间过长,会导致程序启动慢。
以上便是本期特殊类设计的所有内容。
本期内容到此结束^_^