目录
前言
一、设计一个类,不能被拷贝
C++98方法
C++11方法
二、设计一个类,只能在堆上创建对象
方法一
方法二
三、设计一个类,只能在栈上创建对象
方法一
方法二
四、设计一个类,不能被继承
C++98方式
C++11方法
五、设计一个类,只能创建一个对象(单例模式)
设计模式
单例模式:
饿汉模式
懒汉模式
前言
面试中,考官有时候会问一些特殊类的设计,今天我们来介绍一下常见的特殊类的设计方式。
一、设计一个类,不能被拷贝
C++98方法
//无法拷贝的类
class CopyBan
{
public:
CopyBan()
{}
private:
CopyBan(const CopyBan&);
CopyBan& operator=(const CopyBan&);
};
int main()
{
CopyBan cb1;
CopyBan cb2(cb1);
return 0;
}
原因:
C++11方法
//无法拷贝的类
class CopyBan
{
public:
CopyBan()
{}
private:
CopyBan(const CopyBan&) = delete;
CopyBan& operator=(const CopyBan&) = delete;
};
二、设计一个类,只能在堆上创建对象
方法一
1. 将类的构造函数私有,拷贝构造声明成私有。防止别人调用拷贝在栈上生成对象。2. 提供一个静态的成员函数,在该静态成员函数中完成堆对象的创建。 (静态作用在于不需要对象也能直接调用该函数)
//只能在堆区构造的类
class HeapOnly
{
public:
static HeapOnly* Create()
{
return new HeapOnly;
}
private:
HeapOnly(){}
HeapOnly(const HeapOnly&) = delete;
};
int main()
{
HeapOnly* hp = HeapOnly::Create();
}
需要的使用的时候,我们用指针来接收返回的对象。
关于delete释放问题:
因为是我们用new开辟出来的,一般我们是要delete来释放空间的,但是一般情况下用完进程就会回收资源了。只要不是一直跑的程序开辟了空间不回收就行。
方法二
实现方法:
1.将析构函数私有化,因为如果不能调用析构,那么就无法创建对象,编译器会报错。
2.提供一个成员函数,类内调用析构函数销毁对象。
class HeapOnly
{
public:
HeapOnly()
{}
void Destroy()
{
this->~HeapOnly();
}
private:
~HeapOnly(){}
};
三、设计一个类,只能在栈上创建对象
方法一
我们通常用new和delete来创建销毁对象都是其在类内会调用opeartor new 和operator delete,我们如果将其在类内禁用了,那么就无法使用new和delete来创建对象了。
class StackOnly
{
public:
void* operator new(size_t size) = delete;
void operator delete(void* p) = delete;
};
方法二
将构造函数私有,然后提供一个静态函数Create,在函数中调用构造函数。
class StackOnly
{
public:
static StackOnly Creat()
{
return StackOnly();
}
private:
StackOnly(){}
};
分析:
因为我们需要用到拷贝构造,所以不能封掉拷贝构造。 但是我们却可以通过以下的方式创建静态变量。
所以这里设计的只能在栈区创建的对象是有缺陷的。
四、设计一个类,不能被继承
C++98方式
class StackOnly
{
public:
static StackOnly Create()
{
return StackOnly();
}
private:
StackOnly(){}
int _a;
};
class NonInherit : public StackOnly
{
public:
NonInherit()
{}
private:
int _b;
};
C++11方法
class NonInherit final
{
//......
};
五、设计一个类,只能创建一个对象(单例模式)
设计模式
单例模式:
单例模式分为两种:
-
饿汉模式
class Singleton
{
public:
static Singleton& GetInstance()
{
return m_instance;
}
void fun()
{
cout << "Singleton::func()" << endl;
}
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator= (const Singleton&) = delete;
private:
//静态变量
static Singleton m_instance;
};
Singleton Singleton::m_instance; //类外初始化
1.我们在Singleton类中添加一个Singleton类的静态变量,并且在在类外初始化,这样整个类就这一个静态的对象。
2.需要获取的时候我们利用静态函数GetInstance返回,获取到类内的静态对象。
3.我们需要对类内成员进行操作的时候,只需要在类内创建对应的函数即可。
接收我们可以用引用接收:
优缺点:
-
懒汉模式
class Singleton
{
public:
static Singleton& GetInstance()
{
if (m_instance == nullptr)
{
m_instance = new Singleton;
}
return *m_instance;
}
private:
Singleton(){}
Singleton(const Singleton&) {}
Singleton& operator= (const Singleton&) {}
static Singleton* m_instance;
};
Singleton* Singleton::m_instance = nullptr;
懒汉模式的线程安全问题
为了解决这个问题,我们需要对存在线程安全的代码进行加锁。
class Singleton
{
public:
static Singleton& GetInstance()
{
_mtx.lock();
if (m_instance == nullptr)
{
m_instance = new Singleton;
}
_mtx.unlock();
return *m_instance;
}
private:
Singleton(){}
Singleton(const Singleton&) {}
Singleton& operator= (const Singleton&) {}
static Singleton* m_instance;
static mutex _mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::_mtx;
因为只有第一次进if语句的时候才用得上锁,其他的时候也加锁是会浪费性能的。对此我们可以加上一个双检查判断来优化一下。
static Singleton& GetInstance()
{
if (m_instance == nullptr)
{
_mtx.lock();
if (m_instance == nullptr)
{
m_instance = new Singleton;
}
_mtx.unlock();
}
return *m_instance;
}
我们上面的代码中其实还是有一个问题:点那个new失败抛异常之后,锁的unlock操作就不会执行了。为了解决这个问题,我们可以用try-catch的方法来解决,但我们这里还可以用智能指针的办法来自动释放。
利用RAII机制自动回收锁
我们可以创建一个智能锁指针,构造函数中加锁,析构函数中解锁。从而达到了锁的自动加锁和解锁。
class MutexGard
{
public:
MutexGard(mutex& mtx)
:_mtx(mtx)
{
_mtx.lock();
}
~MutexGard()
{
_mtx.unlock();
}
private:
mutex& _mtx; //所不允许拷贝,这里只能用引用
};
class Singleton
{
public:
static Singleton& GetInstance()
{
if (m_instance == nullptr)
{
/*_mtx.lock();
if (m_instance == nullptr)
{
m_instance = new Singleton;
}
_mtx.unlock();*/
MutexGard mg(_mtx);
if (m_instance == nullptr)
{
m_instance = new Singleton;
}
}
return *m_instance;
}
private:
Singleton(){}
Singleton(const Singleton&) {}
Singleton& operator= (const Singleton&) {}
static Singleton* m_instance;
static mutex _mtx;
};
Singleton* Singleton::m_instance = nullptr;
mutex Singleton::_mtx;
资源的回收
一般情况下,资源是不需要我们自动回收的,但有时候,可能要将数据写进文件中进行保存。我们就需要手动在函数内保存了。
void DelInstance()
{
//保存数据的操作
// ......
if (m_instance != nullptr)
{
delete m_instance;
m_instance = nullptr;
}
}
为了省事,我们也可以封装成一个自动保存资源的类,然后在单例类中加入了一个资源回收类的对象,这样在最后调用析构的时候,就能够自动回收资源了。
class GC
{
public:
~GC()
{
//保存数据的操作
// ......
if (m_instance != nullptr)
{
delete m_instance;
m_instance = nullptr;
}
}
};
懒汉模式的简易方法
class Singleton {
public:
static Singleton& GetInstance()
{
static Singleton m_instance;
return m_instance;
}
Singleton(const Singleton& sin) = delete;
Singleton& operator=(const Singleton& sin) = delete;
private:
Singleton() {}
};
这种方法实现的非常简单巧妙:
1.因为是静态的变量,就算反复调用该函数,都只会在静态区开辟一个变量,并不会反复开辟。
2.并不需要在堆区开辟空间创建单例对象。
3.不需要考虑线程安全问题并加锁以及new抛异常问题
上述方法虽然巧妙,但是值得一提的是,只有在C++11之后的版本中才能保证局部创建的静态变量是线程安全的。