目录
一、unique_lock
二、智能指针 (其实是一个类)
三、工厂模式
参考文章【1】,了解unique_lock与lock_guard的区别。
总结:unique_lock使用起来要比lock_guard更灵活,但是效率会第一点,内存的占用也会大一点。
1.unique_lock的定义:
std::mutex mlock;
std::unique_lock munique(mlock);
第二个参数可以是std::adopt_lock
std::unique_lock munique(mlock,std::adopt_lock);
使用unique_lock和mutex来实现互斥锁。unique_lock在构造函数中获取锁,析构函数中释放锁。adopt_lock参数告诉unique_lock构造函数,锁已经被其他线程获取,unique_lock不需要再次获取锁,而是直接将锁的所有权转移给它自己。这样做的好处是可以避免死锁,因为uniquelock的析构函数会在任何情况下都释放锁,即使在发生异常的情况下也是如此。
也可以是:std::try_to_lock
std::unique_lock munique(mlock, std::try_to_lock);
如果有一个线程被锁住,而且执行时间很长,那么另一个线程一般会被阻塞在那里,反而会造成时间的浪费。那么使用了try_to_lock后,如果被锁住了,它不会在那里阻塞等待,它可以先去执行其他没有被锁的代码。
也可以是std::defer_lock
std::unique_lock munique(mlock, std::defer_lock);
表示暂时先不lock,之后手动去lock。一般与unique_lock的成员函数搭配使用
2.unique_lock的成员函数
lock() 与 unlock()
当使用了defer_lock参数时,在创建了unique_lock的对象时就不会自动加锁,那么就需要借助lock这个成员函数来进行手动加锁,当然也有unlock来手动解锁。
try_lock():判断是否能拿到锁,如果拿不到锁,返回false,如果拿到了锁,返回true
release():解除unique_lock和mutex对象的联系,并将原mutex对象的指针返回出来。如果之前的mutex已经加锁,需在后面自己手动unlock解锁
std::unique_lock munique(mlock); // 这里是自动lock
std::mutex *m = munique.release();
....
m->unlock();
std::thread t1(work1, std::ref(ans));
std::ref是C++标准库中的一个函数模板,用于将一个对象包装成一个引用。这个函数模板的作用是将一个对象的引用传递给一个函数,而不是将对象本身传递给函数。这样做的好处是可以避免对象的拷贝,提高程序的效率。
代码块中,std::ref被用于将ans对象包装成一个引用,并将这个引用传递给work1函数的线程。这样做的目的是让work1函数在线程中对ans对象进行操作,而不是对ans对象的拷贝进行操作。
智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。该引用计数的内存在堆上分配。当新增一个时引用计数加1,当过期时引用计数减一。只有引用计数为0时,智能指针才会自动释放引用的内存资源。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类。可以通过make_shared函数或者通过构造函数传入普通指针。并可以通过get函数获得普通指针。
1.unique_ptr
unique_ptr保证同一时间内只有一个智能指针可以指向该对象。它对于避免资源泄露,以new创建对象后因为发生异常而忘记调用delete特别有用。
两个unique_ptr 不能指向一个对象,即 unique_ptr 不共享它所管理的对象。它无法复制到其他 unique_ptr,无法通过值传递到函数。
#include
#include
#include
int main() {
std::unique_ptr ps1, ps2;
ps1 = std::make_unique("hello");
ps2 = std::move(ps1);
ps1 = std::make_unique("alexia");
std::cout << *ps2 << *ps1 << std::endl;
return 0;
}
2.shared_ptr
shared_ptr 允许多个指针指向同一个对象。利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象,当引用计数为 0 的时候,自动释放资源。
成员函数:
use_count 返回引用计数的个数
unique 返回是否是独占所有权
swap 交换两个 shared_ptr 对象(即交换所拥有的对象)
reset 放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
get 返回内部对象(指针), 由于已经重载了()方法, 因此和直接使用对象是一样的.如
shared_ptr sp(new int(1));
sp 与 sp.get()是等价的。
3.weak_ptr
share_ptr虽然已经很好用了,但是有一点share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。
weak_ptr 被设计为与 shared_ptr 共同工作,可以从一个 shared_ptr 或者另一个 weak_ptr 对象构造而来。weak_ptr 是为了配合 shared_ptr 而引入的一种智能指针,它更像是 shared_ptr 的一个助手而不是智能指针,因为它不具有普通指针的行为,没有重载 operator* 和 operator-> ,因此取名为 weak,表明其是功能较弱的智能指针。它的最大作用在于协助 shared_ptr 工作,可获得资源的观测权,像旁观者那样观测资源的使用情况。观察者意味着 weak_ptr 只对 shared_ptr 进行引用,而不改变其引用计数,当被观察的 shared_ptr 失效后,相应的 weak_ptr 也相应失效。
使用方法:
使用 weak_ptr 的成员函数 use_count() 可以观测资源的引用计数。注意:weak_ptr不增加引用计数
另一个成员函数 expired() 的功能等价于 use_count()==0,表示被观测的资源(也就是 shared_ptr 管理的资源)已经不复存在。
weak_ptr 可以使用一个非常重要的成员函数lock(),从被观测的 shared_ptr 获得一个可用的 shared_ptr 管理的对象, 从而操作资源。但当 expired()==true 的时候,lock() 函数将返回一个存储空指针的 shared_ptr。总的来说,weak_ptr 的基本用法总结如下:
shared_ptr sp(new int(1));
weak_ptr w; //创建空 weak_ptr,可以指向类型为 T 的对象
weak_ptr w(sp); //与 shared_ptr 指向相同的对象,shared_ptr 引用计数不变。T必须能转换为 sp 指向的类型
w=p; //p 可以是 shared_ptr 或 weak_ptr,赋值后 w 与 p 共享对象
w.reset(); //将 w 置空
w.use_count(); //返回与 w 共享对象的 shared_ptr 的数量
w.expired(); //若 w.use_count() 为 0,返回 true,否则返回 false
w.lock(); //如果 expired() 为 true,返回一个空 shared_ptr,否则返回非空 shared_ptr
#include
#include
#include
#include
using namespace std;
int main() {
shared_ptr sp(new int(10));
assert(sp.use_count() == 1);
weak_ptr wp(sp); // 从 shared_ptr 创建 weak_ptr
assert(wp.use_count() == 1);
if (!wp.expired()) { // 判断 weak_ptr 观察的对象是否失效
shared_ptr sp2 = wp.lock(); //使用 wp.lock() 创建一个新的 shared_ptr 时,它又增加了一次引用计数
*sp2 = 100;
assert(wp.use_count() == 2);
}
assert(wp.use_count() == 1); //在 if 语句块之外,wp.use_count() 的值仍然是1,因为 weak_ptr 并不会增加引用计数。
cout << "int:" << *sp << endl;
return 0;
}
这段代码主要演示了如何使用 weak_ptr 来避免循环引用的问题。在这个例子中,我们创建了一个 shared_ptr 对象 sp,然后通过 weak_ptr 对象 wp 来观察 sp。如果 sp 被销毁了,那么 wp 也会自动失效。在代码中,我们通过 wp.expired() 来判断 wp 是否失效,如果没有失效,我们就可以通过 wp.lock() 来获得一个 shared_ptr 对象 sp2,然后修改 sp2 所指向的值。最后,我们通过 use_count() 来检查 sp 和 wp 的引用计数是否正确。
4.如何选择智能指针:
(1)如果程序要使用多个指向同一个对象的指针,应选择 shared_ptr
- 将指针作为参数或者函数的返回值进行传递的话,应该使用 shared_ptr;
- 两个对象都包含指向第三个对象的指针,此时应该使用 shared_ptr 来管理第三个对象;
- STL 容器包含指针。很多 STL 算法都支持复制和赋值操作,这些操作可用于 shared_ptr,但不能用于 unique_ptr(编译器发出 warning)和 auto_ptr(行为不确定)。如果你的编译器没有提供 shared_ptr,可使用 Boost 库提供的 shared_ptr。
(2)如果程序不需要多个指向同一个对象的指针,则可使用 unique_ptr。如果函数使用 new 分配内存,并返还指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选择。
(3)为了解决 shared_ptr 的循环引用问题,使用 weak_ptr。
参考【2】【3】
工厂模式分为简单工厂模式、工厂方法模式和抽象工厂模式。
1.简单工厂模式
简单工厂模式又叫静态方法模式(因为工厂类定义了一个静态方法)。
将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,从而避免了在客户端代码中显式指定,实现了解耦。也就是说,使用者可直接消费产品而不需要知道其生产的细节。
模式组成:
组成 | 关系 | 作用 |
抽象产品(Product) | 具体产品的父类 | 描述产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
工厂(Factor) | 被外界调用 | 根据传入不同参数从而创建不同具体产品类的实例 |
使用步骤:
- 创建抽象产品类Product :定义产品的公共接口
- 创建具体产品类(继承抽象产品类):定义生产的具体产品
- 创建工厂类:通过创建静态方法传入不同参数,从而创建不同具体产品类的实例
- 外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
实列:某加工厂推出三个产品,使用简单工厂模式实现三种产品的生成
①创建抽象产品类Product:
class Product
{
public:
virtual void Show() = 0;
};
②创建具体产品类(继承抽象产品类):
class ProductA : public Product
{
public:
void Show()
{
cout<<"I'm ProductA"<
③创建工厂类:通过创建静态方法传入不同参数,从而创建不同具体产品类的实例。
typedef enum ProductTypeTag
{
TypeA,
TypeB,
TypeC
}PRODUCTTYPE;
class Factory
{
public:
static Product* CreateProduct(PRODUCTTYPE type)
{
switch (type)
{
case TypeA:
return new ProductA();
case TypeB:
return new ProductB();
case TypeC:
return new ProductC();
default:
return NULL;
}
}
};
④外界通过调用工厂类的静态方法,传入不同参数从而创建不同具体产品类的实例
int main(int argc, char *argv[])
{
//创造工厂对象
Factory *ProductFactory = new Factory();
//从工厂对象创造产品A对象
Product *productObjA = ProductFactory->CreateProduct(TypeA);
if (productObjA != NULL)
productObjA->Show();
Product *productObjB = ProductFactory->CreateProduct(TypeB);
if (productObjB != NULL)
productObjB->Show();
Product *productObjC = ProductFactory->CreateProduct(TypeC);
if (productObjC != NULL)
productObjC->Show();
delete ProductFactory;
ProductFactory = NULL;
delete productObjA;
productObjA = NULL;
delete productObjB;
productObjB = NULL;
delete productObjC;
productObjC = NULL;
return 0;
}
优缺点:
优点:
- 把初始化实例时的工作放到工厂里进行,使代码更容易维护。
- 将“类实例化的操作”与“使用对象的操作”分开,让使用者不用知道具体参数就可以实例化出所需要的“产品”类,
缺点:
- 工厂类集中了所有实例(产品)的创建逻辑,一旦这个工厂不能正常工作,整个系统都会受到影响;
- 违背“开放 - 关闭原则”,一旦添加新产品就不得不修改工厂类的逻辑,这样就会造成工厂逻辑过于复杂。
- 简单工厂模式由于使用了静态工厂方法,静态方法不能被继承和重写,会造成工厂角色无法形成基于继承的等级结构。
2.工厂方法模式
针对简答工厂模式问题,设计了工厂方法模式。工厂父类负责定义创建对象的公共接口,而子类则负责生成具体的对象。
将类的实例化(具体产品的创建)延迟到工厂类的子类(具体工厂)中完成,即由子类来决定应该实例化(创建)哪一个类。
之所以可以解决简单工厂的问题,是因为工厂方法模式把具体产品的创建推迟到工厂类的子类(具体工厂)中,此时工厂类不再负责所有产品的创建,而只是给出具体工厂必须实现的接口,这样工厂方法模式在添加新产品的时候就不修改工厂类逻辑而是添加新的工厂子类,符合开放封闭原则。
模式组成:
组成 | 关系 | 作用 |
抽象产品(Product) | 具体产品的父类 | 描述具体产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
抽象工厂(Factor) | 具体工厂的父类 | 描述具体工厂的公共接口 |
具体工厂(Concrete Factor) | 抽象工厂的子类;被外界调用 | 描述具体工厂 |
使用步骤:
- 创建抽象产品类 ,定义具体产品的公共接口;
- 创建具体产品类(继承抽象产品类):定义生产的具体产品;
- 创建抽象工厂类,定义具体工厂的公共接口;
- 创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
- 外界通过调用具体工厂类的方法,从而创建不同具体产品类的实例
实例:某加工厂原来只生产A类产品,但新的订单要求生产B类产品,由于改变原来工厂的配置比较困难,因此开设工厂B生产B类产品。
#include
using namespace std;
//1.创建抽象产品类
class Product
{
public:
virtual void Show() = 0;
};
//2.创建具体产品类(继承抽象产品类)
class ProductA : public Product
{
public:
void Show()
{
cout<< "I'm ProductA"<CreateProduct();
productA->Show();
Factory *factoryB = new FactoryB ();
Product *productB = factoryB->CreateProduct();
productB->Show();
if (factoryA != NULL)
{
delete factoryA;
factoryA = NULL;
}
if (productA != NULL)
{
delete productA;
productA = NULL;
}
if (factoryB != NULL)
{
delete factoryB;
factoryB = NULL;
}
if (productB != NULL)
{
delete productB;
productB = NULL;
}
system("pause");
return 0;
}
优缺点:
优点:
- 符合开-闭原则。新增一种产品时,只需要增加相应的具体产品类和相应的工厂子类即可。(简单工厂模式需要修改工厂类的判断逻辑)
- 符合单一职责原则。每个具体工厂类只负责创建对应的产品。(简单工厂中的工厂类存在复杂的switch逻辑判断)
- 不使用静态工厂方法,可以形成基于继承的等级结构。(简单工厂模式的工厂类使用静态工厂方法)
缺点:
- 添加新产品时,除了增加新产品类外,还要提供与之对应的具体工厂类,系统类的个数将成对增加,在一定程度上增加了系统的复杂度
- 由于考虑到系统的可扩展性,需要引入抽象层,在客户端代码中均使用抽象层进行定义,增加了系统的抽象性和理解难度
- 一个具体工厂只能创建一种具体产品
3.抽象工厂模式
工厂方法模式存在一个严重的问题:一个具体工厂只能创建一类产品;而在实际过程中,一个工厂往往需要生产多类产品。使用了一种新的设计模式:抽象工厂模式。
定义:创建一系列相关或相互依赖对象的接口,而无须指定它们具体的类;具体的工厂负责实现具体的产品实例。
允许使用抽象的接口来创建一组相关产品,而不需要知道或关心实际生产出的具体产品是什么,这样就可以从具体产品中被解耦。
模式组成:
组成 | 关系 | 作用 |
抽象产品族(AbstractProduct | 抽象产品的父类 | 描述抽象产品的公共接口 |
抽象产品(Product) | 具体产品的父类 | 描述具体产品的公共接口 |
具体产品(Concrete Product) | 抽象产品的子类;工厂类创建的目标类 | 描述生产的具体产品 |
抽象工厂(Factor) | 具体工厂的父类 | 描述具体工厂的公共接口 |
具体工厂(Concrete Factor) | 抽象工厂的子类;被外界调用 | 描述具体工厂 |
使用步骤:
- 创建抽象产品族类 ,定义抽象产品的公共接口;
- 创建抽象产品类 (继承抽象产品族类),定义具体产品的公共接口;
- 创建具体产品类(继承抽象产品类) & 定义生产的具体产品;
- 创建抽象工厂类,定义具体工厂的公共接口;
- 创建具体工厂类(继承抽象工厂类),定义创建对应具体产品实例的方法;
- 客户端通过实例化具体的工厂类,并调用其创建不同目标产品的方法 创建不同具体产品类的实例
实例:某厂有两个加工厂A和B分别生产鞋和衣服,随着订单的增加,A厂所在地有了衣服的订单,B厂所在地有了鞋子的订单,再开新的工厂显然是不现实的,因此在原来的工厂增加生产需求的功能,即A生产鞋+衣服,B生产衣服+鞋。
#include
using namespace std;
//这个代码没有抽象产品族类
// 抽象产品A
class ProductA
{
public:
virtual void Show() = 0;
};
//具体的产品
class ProductA1 : public ProductA
{
public:
void Show()
{
cout<<"I'm ProductA1"<CreateProductA();
ProductB *productObjB1 = factoryObj1->CreateProductB();
productObjA1->Show();
productObjB1->Show();
Factory *factoryObj2 = new Factory2();
ProductA *productObjA2 = factoryObj2->CreateProductA();
ProductB *productObjB2 = factoryObj2->CreateProductB();
productObjA2->Show();
productObjB2->Show();
if (factoryObj1 != NULL)
{
delete factoryObj1;
factoryObj1 = NULL;
}
if (productObjA1 != NULL)
{
delete productObjA1;
productObjA1= NULL;
}
if (productObjB1 != NULL)
{
delete productObjB1;
productObjB1 = NULL;
}
if (factoryObj2 != NULL)
{
delete factoryObj2;
factoryObj2 = NULL;
}
if (productObjA2 != NULL)
{
delete productObjA2;
productObjA2 = NULL;
}
if (productObjB2 != NULL)
{
delete productObjB2;
productObjB2 = NULL;
}
system("pause");
return 0;
}
优缺点:
优点:
- 抽象工厂模式将具体产品的创建延迟到具体工厂的子类中,这样将对象的创建封装起来,可以减少客户端与具体产品类之间的依赖,从而使系统耦合度低,这样更有利于后期的维护和扩展;
- 新增一种产品类时,只需要增加相应的具体产品类和相应的工厂子类即可
缺点:
- 抽象工厂模式很难支持新种类产品的变化。因为抽象工厂接口中已经确定了可以被创建的产品集合,如果需要添加新产品,此时就必须去修改抽象工厂的接口,这样就涉及到抽象工厂类的以及所有子类的改变
参考【4】 【5】