特殊类设计与单例模式
- 一、不能被拷贝的类
- 1、介绍
- 2、示例代码
- 二、只能在堆上创建对象的类
- 1、介绍
- 2、示例代码
- 三、只能在栈上创建对象的类
- 1、介绍
- 2、示例代码
- 四、单例模式
- 1、介绍
- 2、设计模式
- 3、懒汉式
- (1)介绍
- (2)示例代码1
- (3)运行结果
- (4)示例代码2
- (5)说明
- 4、饿汉式
- (1)介绍
- (2)示例代码
一、不能被拷贝的类
1、介绍
- 在C++中,设计一个类以禁止拷贝通常是通过声明拷贝构造函数和拷贝赋值运算符为delete来完成的。这样做的目的是为了防止该类的对象被不当地复制,这在处理一些拥有独特资源(如文件句柄、网络连接等)的类中特别有用。
- 尽管禁用了拷贝,但仍然可以移动一个NonCopyable对象(除非也禁用了移动构造函数和移动赋值运算符)。
- 在很多情况下,保留移动语义是有益的,因为它允许在不涉及实际资源复制的情况下传递或返回对象。
2、示例代码
class NonCopyable
{
public:
// 默认构造函数
NonCopyable() = default;
// 声明拷贝构造函数为delete,防止拷贝
NonCopyable(const NonCopyable&) = delete;
// 声明拷贝赋值运算符为delete,防止赋值拷贝
NonCopyable& operator=(const NonCopyable&) = delete;
// 默认析构函数,因为类中不包含任何需要显式清理的资源
~NonCopyable() = default;
// 如果需要,还可以声明移动构造函数和移动赋值运算符
// NonCopyable(NonCopyable&&) = default;
// NonCopyable& operator=(NonCopyable&&) = default;
// 但由于通常禁用了拷贝,保留移动语义是合理的
};
int main()
{
NonCopyable obj1;
// 以下尝试将导致编译错误
// NonCopyable obj2 = obj1; // 拷贝构造函数被删除
// NonCopyable obj3;
// obj3 = obj1; // 拷贝赋值运算符被删除
return 0;
}
二、只能在堆上创建对象的类
1、介绍
- 在C++中,设计一个类使其对象只能在堆上创建,可以通过将类的构造函数声明为private或protected,并提供一个静态成员函数来返回指向新创建对象的指针(通常使用new关键字)来实现。
- 此外,还可以将析构函数声明为public(或protected,如果类有继承关系),但通常不需要将其设为private,因为这样做会阻止对象被正常销毁(尽管可以通过智能指针(参见智能指针(RAII))来间接管理)。
2、示例代码
class HeapOnly
{
public:
static HeapOnly* CreateObject()
{
return new HeapOnly;
}
HeapOnly(const HeapOnly& hp) = delete;
HeapOnly& operator=(const HeapOnly&) = delete;
private:
HeapOnly()
{
cout << "HeapOnly()" << endl;
}
};
int main()
{
HeapOnly* hp3 = HeapOnly::CreateObject();
}
三、只能在栈上创建对象的类
1、介绍
- 在C++中,设计一个类使其对象只能在栈上创建,通常的做法是限制该类的动态内存分配。然而,C++标准本身并不直接提供机制来禁止对象在堆上创建(即使用new操作符)。不过,可以通过一些技巧来使得在堆上创建对象变得不切实际或难以维护。
- 一个常见的策略是将析构函数声明为私有(或受保护,如果打算有子类),并确保没有提供公共的拷贝构造函数、拷贝赋值运算符、移动构造函数或移动赋值运算符(除非它们也被设计为受限的)。然而,这仍然不会阻止用户在堆上创建对象,因为用户仍然可以调用类的私有构造函数(通过友元函数或模板特化等技巧)。
- 但是,一个更实际的做法是使类在堆上创建时变得不安全或不合理。然而,这些方法都不是强制性的。在C++中,真正的只能在栈上创建的约束是不可能的,因为C++的设计哲学是给予程序员足够的自由。
- 还有另一个办法,因为C++在堆上创建对象需要使用new进行操作,这时可以在类的内部重载new操作符。当使用new时,默认会优先使用类内部的重载函数,而将这个重载函数声明为删除,就能使得该类不能在堆上创建对象。
2、示例代码
class StackOnly
{
public:
static StackOnly CreateObject()
{
StackOnly so;
return so;
}
void* operator new(size_t) = delete;
/*void* operator new(size_t sz)
{
cout << "void* operator new(size_t sz)" << endl;
return malloc(sz);
}*/
private:
StackOnly()
{
cout << "StackOnly()" << endl;
}
};
int main()
{
StackOnly so1 = StackOnly::CreateObject();
//StackOnly* so2 = new StackOnly(so1);
//StackOnly* so3 = new StackOnly;
return 0;
}
四、单例模式
1、介绍
- 单例模式(Singleton Pattern)是一种常用的软件设计模式,它确保一个类仅有一个实例,并提供一个全局访问点来获取这个实例。
- C++中的单例模式可以实现为懒汉式(Lazy Initialization)和饿汉式(Eager Initialization)两种形式。这两种形式的主要区别在于实例化的时机。
- 在C++中实现单例模式,需要确保以下关键点:
- 私有构造函数:防止外部通过new关键字直接创建类的实例。
- 私有静态成员变量:存储类的唯一实例。
- 公有静态方法:提供一个全局访问点来获取类的唯一实例。
2、设计模式
- 设计模式(Design Patterns)是软件开发人员在软件设计过程中面临的一般问题的解决方案。它们不是可以直接使用的代码,而是描述在特定上下文中常见问题的最佳实践和设计方法的模板。使用设计模式可以帮助开发人员构建可重用、可维护、可理解和可扩展的软件系统。
- 设计模式大致可以分为创建型模式、结构型模式和行为型模式。
- 创建型模式(Creational Patterns):这类模式主要关注对象的创建过程。它们提供了在创建对象时隐藏创建逻辑的方式,使得系统更加灵活,易于扩展。而单例模式就属于创建型模式的一种。
- 结构型模式(Structural Patterns):结构型模式关注于如何通过组合类与对象来获得更大的结构。它们提供了一种方式来组织软件中的对象,以便更灵活地设计不同的软件结构。
- 行为型模式(Behavioral Patterns):行为型模式关注于对象之间的交互和职责的分配。它们定义了对象如何通信以及对象如何分配职责。
3、懒汉式
(1)介绍
- 懒汉式(Lazy Initialization)单例模式在类的静态成员变量被首次使用时才创建实例。
- 这种方式在资源密集型或实例化成本较高的场景中很有用,因为它延迟了实例的创建直到真正需要它的时候。但是,它必须确保在多线程环境下是线程安全的。
(2)示例代码1
class idler
{
public:
static idler* GetInstance()
{
if (_pi == nullptr)
{
unique_lock<mutex> ul(_mtx);
if(_pi == nullptr)
_pi = new idler;
}
return _pi;
}
void AddElement(const string& key, const string& value)
{
_dict[key] = value;
}
void Print()
{
for (auto& e : _dict)
{
cout << e.first << ":" << e.second << endl;
}
cout << endl;
}
static void DeleteIdler()
{
if (_pi)
{
delete _pi;
_pi = nullptr; //最好置为空
cout << "DeleteIdler(),此函数可进行持久化操作" << endl;
}
}
private:
idler()
{}
~idler()
{
cout << "此处可进行持久化操作" << endl;
}
idler(const idler&) = delete;
idler& operator=(const idler&) = delete;
struct gc
{
~gc()
{
DeleteIdler();
}
};
map<string, string> _dict;
static idler* _pi;
static gc _gc;
static mutex _mtx;
};
idler *idler::_pi = nullptr;
idler::gc idler::_gc;
mutex idler::_mtx;
void TestIdler()
{
idler::GetInstance()->AddElement("snow", "雪");
idler::GetInstance()->AddElement("dragon", "龙");
idler::GetInstance()->AddElement("snowdragon", "雪龙");
idler::GetInstance()->Print();
}
(3)运行结果
(4)示例代码2
class idler2
{
public:
static idler2& GetInstance()
{
static idler2 id2;
return id2;
}
private:
idler2()
{
cout << "idler2()" << endl;
}
~idler2()
{
cout << "此处可进行持久化操作" << endl;
}
idler2(const idler2&) = delete;
idler2& operator=(const idler2&) = delete;
};
void TestIdler2()
{
idler2::GetInstance();
}
(5)说明
在上方代码中的GetInstance中,id2是局部的静态对象,在第一次调用时才进行初始化操作。
4、饿汉式
(1)介绍
- 饿汉式(Eager Initialization)单例模式在程序启动时,即在类加载的时候就创建了类的实例。
- 这种方式简化了实现,并且因为是静态初始化,所以自动是线程安全的(在C++11及以后的标准中,静态局部变量的初始化是线程安全的)。但是,它可能会在不需要实例的时候就创建了实例,即浪费资源。
(2)示例代码
class Singleton
{
public:
static Singleton& getInstance()
{
return instance;
}
void doSomething()
{
std::cout << "Doing something..." << std::endl;
}
private:
static Singleton instance;
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;
int main()
{
Singleton& s1 = Singleton::getInstance();
s1.doSomething();
return 0;
}
本文到这里就结束了,如有错误或者不清楚的地方欢迎评论或者私信
创作不易,如果觉得博主写得不错,请点赞、收藏加关注支持一下💕💕💕