01 对象被优化以后才是高效的C++编程
1. 对象使用过程中背后调用了哪些方法
示例一:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a = 10) :ma(a)
{cout << "Test()" << endl;}
~Test()
{cout << "~Test()" << endl;}
Test(const Test &t) :ma(t.ma)
{cout << "Test(const Test&)" << endl;}
Test& operator=(const Test &t)
{
cout << "operator=" << endl;
ma = t.ma;
return *this;
}
private:
int ma;
};
int main()
{
Test t1; // 调用 Test()
Test t2(t1); // 调用 Test(const Test&)
Test t3 = t1; // 调用 Test(const Test&)
// Test(20) 显式生成临时对象,临时对象的生存周期为其所在的语句
/*
C++编译器对于对象构造的优化:
用临时对象拷贝构造生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了。
*/
Test t4 = Test(20); // 等同于 Test t4(20); 调用 Test()
cout << "------------------" << endl;
// t4.operator=(t2);
t4 = t2;
/*
先调用构造函数Test()显式生成临时对象,然后将临时对象赋值给t4,
最后通过析构函数~Test()释放临时对象。
*/
t4 = Test(30);
/*
将 int 强制转换为 Test(int), 先调用构造函数Test()生成临时对象,
然后将临时对象赋值给t4,最后通过析构函数~Test()释放临时对象。
*/
t4 = (Test)30;
/*
将 int 强制转换为 Test(int), 先调用构造函数Test()隐式生成临时对象,
然后将临时对象赋值给t4,最后通过析构函数~Test()释放临时对象。
*/
t4 = 30;
cout << "------------------" << endl;
/*
先调用构造函数Test()显式生成临时对象,该语句执行完成以后,
通过析构函数~Test()释放临时对象,p指向的是一个已经析构的临时对象。
*/
Test *p = &Test(40);
/*
先调用构造函数Test()显式生成临时对象,然后引用临时对象,
临时对象的生命周期就变为引用变量的生命周期。
用指针指向临时对象是不安全的,但是可以使用引用变量引用临时对象。
*/
const Test &ref = Test(50);
cout << "------------------" << endl;
return 0;
}
注意⚠️:
C++编译器对于对象构造的优化:用临时对象拷贝构造生成新对象的时候,临时对象就不产生了,直接构造新对象就可以了。
运行结果为:
Test()
Test(const Test&)
Test(const Test&)
Test()
------------------
operator=
Test()
operator=
~Test()
Test()
operator=
~Test()
Test()
operator=
~Test()
------------------
Test()
~Test()
Test()
------------------
~Test()
~Test()
~Test()
~Test()
~Test()
示例二:
#include <iostream>
using namespace std;
class Test
{
public:
Test(int a = 5, int b = 5) :ma(a), mb(b)
{
cout << "Test(int, int)" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
Test(const Test &src) :ma(src.ma), mb(src.mb)
{
cout << "Test(const Test&)" << endl;
}
void operator=(const Test &src)
{
cout << "operator=" << endl;
ma = src.ma;
mb = src.mb;
}
private:
int ma;
int mb;
};
Test t1(10, 10); // 1.Test(int, int) 20. ~Test()
int main()
{
Test t2(20, 20); // 3.Test(int, int) 17. ~Test()
Test t3 = t2; // 4.Test(const Test&) 16. ~Test()
// 等同于static Test t4(30, 30);
static Test t4 = Test(30, 30); // 5.Test(int, int) 18. ~Test()
t2 = Test(40, 40); // 6.Test(int, int)、operator=、~Test()
// (Test)(50, 50) = (Test)50; Test(int)
t2 = (Test)(50, 50); // 7.Test(int, int)、operator=、~Test()
t2 = 60; // Test(int) 8.Test(int, int)、operator=、~Test()
Test *p1 = new Test(70, 70); // 9. Test(int, int)
Test *p2 = new Test[2]; // 10. Test(int, int)、Test(int, int)
Test *p3 = &Test(80, 80); // 11. Test(int, int)、~Test()
const Test &p4 = Test(90, 90); // 12. Test(int, int) 15. ~Test()
delete p1; // 13. ~Test()
delete []p2; // 14. ~Test() ~Test()
}
Test t5(100, 100); // 2.Test(int, int) 19. ~Test()
运行结果为:
Test(int, int)
Test(int, int)
Test(int, int)
Test(const Test&)
Test(int, int)
Test(int, int)
operator=
~Test()
Test(int, int)
operator=
~Test()
Test(int, int)
operator=
~Test()
Test(int, int)
Test(int, int)
Test(int, int)
Test(int, int)
~Test()
Test(int, int)
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
~Test()
2. 函数调用过程中对象背后调用的方法太多
注意⚠️:函数调用,实参传递给形参的过程,实际上是初始化形参的过程。
#include <iostream>
using namespace std;
class Test
{
public:
// Test() Test(20)
Test(int data = 10) :ma(data)
{
cout << "Test(int)" << endl;
}
~Test()
{
cout << "~Test()" << endl;
}
Test(const Test &src) :ma(src.ma)
{
cout << "Test(const Test&)" << endl;
}
void operator=(const Test &src)
{
cout << "operator=" << endl;
ma = src.ma;
}
int getData()const {return ma;}
private:
int ma;
};
// 不能返回局部的或者临时对象的指针或引用
Test GetObject(Test t) // 3. 调用 Test(const Test&),由t1拷贝构造t 7. ~Test()
{
int val = t.getData();
Test tmp(val); // 4. Test() 6. ~Test()
return tmp; // 5. 调用 Test(const Test&),由tmp在main()函数上,拷贝构造一个临时对象
}
int main()
{
Test t1; // 1. Test() 11. ~Test()
Test t2; // 2. Test() 10. ~Test()
t2 = GetObject(t1); // 8. operator=,把临时对象赋值给t2
// 9. 调用~Test(),析构临时对象
return 0;
}
运行结果为:
Test(int)
Test(int)
Test(const Test&)
Test(int)
Test(const Test&)
~Test()
~Test()
operator=
~Test()
~Test()
~Test()
3. 总结三条对象优化的原则
- 函数参数传递过程中,对象优先按引用传递,不要按值传递。
- 函数返回对象的时候,应该优先返回一个临时对象,而不要返回一个定义过的对象。
- 当调用函数的返回值是对象的时候,优先按初始化的方式接收返回值,不要按赋值的方式接收。
4. CMyString的代码问题/添加带右值引用参数的拷贝构造和赋值函数
#include <iostream>
#include <cstring>
using namespace std;
class CMyString
{
public:
CMyString(const char *str = nullptr)
{
cout << "CMyString(const char*)" << endl;
if (str != nullptr)
{
mptr = new char[strlen(str) + 1];
strcpy(mptr, str);
}
else
{
mptr = new char[1];
*mptr = '\0';
}
}
~CMyString()
{
cout << "~CMyString" << endl;
delete []mptr;
mptr = nullptr;
}
// 带左值引用参数的拷贝构造函数
CMyString(const CMyString& str)
{
cout << "CMyString(const CMyString&)" << endl;
mptr = new char[strlen(str.mptr) + 1];
strcpy(mptr, str.mptr);
}
// 带右值引用参数的拷贝构造函数
CMyString(CMyString&& str)
{
cout << "CMyString(CMyString&&)" << endl;
mptr = str.mptr;
str.mptr = nullptr;
}
// 带左值引用参数的赋值重载函数
CMyString& operator=(const CMyString &str)
{
cout << "operator=(const CMyString&)" << endl;
if (this == &str)
return *this;
delete[] mptr;
mptr = new char[strlen(str.mptr) + 1];
strcpy(mptr, str.mptr);
return *this;
}
// 带右值引用参数的赋值重载函数
CMyString& operator=(CMyString &&str)
{
cout << "operator=(CMyString&&)" << endl;
if (this == &str)
return *this;
delete[] mptr;
mptr = str.mptr;
str.mptr = nullptr;
return *this;
}
const char* c_str()const {return mptr;}
private:
char* mptr;
};
CMyString GetString(CMyString &str)
{
const char* pstr = str.c_str();
CMyString tmpStr(pstr);
return tmpStr;
}
int main()
{
CMyString str1("aaaaaaaaaaaaaa");
CMyString str2;
str2 = GetString(str1);
cout << str2.c_str() << endl;
return 0;
}
/*
不引入带右值引用参数的拷贝构造和赋值重载函数的运行结果为:
CMyString(const char*)
CMyString(const char*)
CMyString(const char*)
CMyString(const CMyString&) => tmpStr拷贝构造main函数栈帧上的临时对象
~CMyString
operator=(const CMyString&) => main函数栈帧上的临时对象给t2赋值
~CMyString
aaaaaaaaaaaaaa
~CMyString
~CMyString
*/
在代码中引入了带右值引用参数的拷贝构造函数和赋值重载函数,减少了内存开辟、内存释放和数据拷贝的开销。
运行结果为:
CMyString(const char*)
CMyString(const char*)
CMyString(const char*)
CMyString(const CMyString&&)
~CMyString
operator=(CMyString&&)
~CMyString
aaaaaaaaaaaaaa
~CMyString
~CMyString
5. move移动语义和forward类型完美转发
move
:移动语义,得到右值类型。std::move(val)
,将val
的类型强转为右值类型。forward
:类型完美转发,能够识别左值和右值类型。std::forward<T>(val)
,能够识别val
的类型T
是左值还是右值。
02 体验一下智能指针的强大
1. 基础知识
-
智能指针:保证能做到资源的自动释放
-
利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放的。
-
不能把智能指针定义在堆上,因为把智能指针定义在堆上,就需要手动对智能指针指向的资源进行释放。
#include <iostream>
using namespace std;
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T *ptr = nullptr):mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator* () { return *mptr; }
T* operator->() { return mptr; }
private:
T *mptr;
};
int main()
{
CSmartPtr<int> ptr1(new int);
*ptr1 = 20;
class Test
{
public:
void test() { cout << "call Test::test" << endl;}
};
CSmartPtr<Test> ptr2(new Test());
ptr2->test();
return 0;
}
2. 不带引用计数的智能指针(推荐使用unique_ptr)
- C++库里面:
auto_ptr
,具有unique_ptr的部分特性,我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr,因此不推荐使用auto_ptr。
#include <iostream>
#include <memory>
using namespace std;
int main()
{
/*
不带引用计数的智能指针(推荐使用unique_ptr)
C++库里面:auto_ptr具有unique_ptr的部分特性,我们不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。不推荐使用auto_ptr。
*/
auto_ptr<int> ptr1(new int);
auto_ptr<int> ptr2(ptr1); // 相当于ptr2(ptr1.release()),将ptr1赋值给ptr2,再将ptr1置为nullptr。
*ptr2 = 20;
// cout << *ptr1 << endl; ptr1为nullptr,不能进行解引用。
return 0;
}
-
C++11新标准:
scoped_ptr
、unique_ptr
-
scoped_ptr
类中直接删除了拷贝构造函数和赋值重载函数scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete; scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;
-
unique_ptr
类中也删除了拷贝构造函数和赋值重载函数unique_ptr(const unique_ptr<T>&) = delete; unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;
-
unique_ptr
类中提供了带右值的拷贝构造函数和赋值重载函数unique_ptr(unique_ptr<T> &&src) unique_ptr<T>& operator=(unique_ptr<T> &&src)
-
因此,可以如下定义
unique_ptr
unique_ptr<int> p1(new int); unique_ptr<int> p2(std::move(p1)); // std::move得到当前变量的右值类型
-
3. 实现带引用计数的智能指针
- 带引用计数的智能指针
shared_ptr
和weak_ptr
。 - 带引用计数:多个智能指针可以管理同一个资源,给每一个对象资源匹配一个引用计数。
- 当我们拷贝一个
shared_ptr
,计数器递增。当我们给shared_ptr
赋予一个新值或是shared_ptr
被销毁时,计数器就会递减。一旦一个shared_ptr
的计数器变为0
,它就会自动释放自己所管理的对象。
4. shared_ptr的交叉引用问题
-
C++11新标准:
shared_ptr
、weak_ptr
-
shared_ptr
:强智能指针,可以改变资源的引用计数。 -
weak_ptr
:弱智能指针,不会改变资源的引用计数。 -
weak_ptr
==》shared_ptr
==》 资源(内存)
强智能指针循环引用(交叉引用)是什么?会造成什么结果?怎么解决?
#include <iostream>
#include <memory>
using namespace std;
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1
shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1
ptra->_ptrb = ptrb;// A对象的成员变量_ptrb也指向B对象,B的引用计数为2
ptrb->_ptra = ptra;// B对象的成员变量_ptra也指向A对象,A的引用计数为2
cout << ptra.use_count() << endl; // 打印A的引用计数结果:2
cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2
/*
出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和
B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是
A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放,
导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题”
*/
return 0;
}
运行结果:
A()
B()
2
2
出现的问题:A和B对象并没有进行析构,通过上面的代码示例,能够看出来“交叉引用”的问题所在,就是对象无法析构,强智能指针循环引用导致new
出来的资源无法释放,造成资源泄露的问题。
解决办法:定义对象的地方,使用强智能指针;引用对象的地方,使用弱智能指针。
弱智能指针weak_ptr区别于shared_ptr之处在于:
weak_ptr
不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr
来判定资源是否存在weak_ptr
持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数weak_ptr
没有提供常用的指针操作,无法直接访问资源,需要先通过lock()
方法提升为shared_ptr
强智能指针,才能访问资源
将引用对象的地方,修改为弱指针weak_ptr
后的运行结果:
A()
B()
1
1
~B()
~A()
注意⚠️:由于对象可能不存在,我们不能使用 weak_ptr
直接访问对象,而必须调用 lock
。该函数检查 weak_ptr
指向的对象是否仍存在。如果存在,lock
返回一个指向共享对象的 shared_ptr
。与任何其他 shared_ptr
类似,只要此 shared_ptr
存在,它所指向的底层对象也就会一直存在。例如:
auto p = make_shared<int> (42);
weak_ptr<int> wp(p); // wp弱共享p,p的引用计数未改变
if (shared_ptr<int> np = wp.lock()) // 如果np不为空,则条件成立
{
// 在if中,np与p共享对象
}
5. 多线程访问共享对象的线程安全问题
有一个用C++写的开源网络库,muduo库,作者陈硕,大家可以在网上下载到muduo的源代码,该源码中对于智能指针的应用非常优秀,其中借助shared_ptr和weak_ptr解决了这样一个问题,多线程访问共享对象的线程安全问题,解释如下:线程A和线程B访问一个共享的对象,如果线程A正在析构这个对象的时候,线程B又要调用该共享对象的成员方法,此时可能线程A已经把对象析构完了,线程B再去访问该对象,就会发生不可预期的错误。
先看如下代码:
#include <iostream>
#include <thread>
using namespace std;
class Test
{
public:
// 构造Test对象,_ptr指向一块int堆内存,初始值是20
Test() :_ptr(new int(20))
{
cout << "Test()" << endl;
}
// 析构Test对象,释放_ptr指向的堆内存
~Test()
{
delete _ptr;
_ptr = nullptr;
cout << "~Test()" << endl;
}
// 该show会在另外一个线程中被执行
void show()
{
cout << *_ptr << endl;
}
private:
int *volatile _ptr;
};
void threadProc(Test *p)
{
// 睡眠两秒,此时main主线程已经把Test对象给delete析构掉了
std::this_thread::sleep_for(std::chrono::seconds(2));
/*
当前线程访问了main线程已经析构的共享对象,结果未知,隐含bug。
此时通过p指针想访问Test对象,需要判断Test对象是否存活,如果Test对象
存活,调用show方法没有问题;如果Test对象已经析构,调用show有问题!
*/
p->show();
}
int main()
{
// 在堆上定义共享对象
Test *p = new Test();
// 使用C++11的线程类,开启一个新线程,并传入共享对象的地址p
std::thread t1(threadProc, p);
// 在main线程中析构Test共享对象
delete p;
// 等待子线程运行结束
t1.join();
return 0;
}
运行上面的代码,发现在main
主线程已经delete
析构Test
对象以后,子线程threadProc
再去访问Test
对象的show
方法,无法打印出*_ptr
的值20。可以用shared_ptr
和weak_ptr
来解决多线程访问共享对象的线程安全问题,上面代码修改如下:
#include <iostream>
#include <thread>
#include <memory>
using namespace std;
class Test
{
public:
// 构造Test对象,_ptr指向一块int堆内存,初始值是20
Test() :_ptr(new int(20))
{
cout << "Test()" << endl;
}
// 析构Test对象,释放_ptr指向的堆内存
~Test()
{
delete _ptr;
_ptr = nullptr;
cout << "~Test()" << endl;
}
// 该show会在另外一个线程中被执行
void show()
{
cout << *_ptr << endl;
}
private:
int *volatile _ptr;
};
void threadProc(weak_ptr<Test> pw) // 通过弱智能指针观察强智能指针
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
/*
如果想访问对象的方法,先通过pw的lock方法进行提升操作,把weak_ptr提升
为shared_ptr强智能指针,提升过程中,是通过检测它所观察的强智能指针保存
的Test对象的引用计数,来判定Test对象是否存活,ps如果为nullptr,说明Test对象
已经析构,不能再访问;如果ps!=nullptr,则可以正常访问Test对象的方法。
*/
shared_ptr<Test> ps = pw.lock();
if (ps != nullptr)
{
ps->show();
}
}
int main()
{
// 在堆上定义共享对象
shared_ptr<Test> p(new Test);
// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
std::thread t1(threadProc, weak_ptr<Test>(p));
// 在main线程中析构Test共享对象
// 等待子线程运行结束
t1.join();
return 0;
}
运行上面的代码,show方法可以打印出20,因为main线程调用了t1.join()方法等待子线程结束,此时pw通过lock提升为ps成功,见上面代码示例。
如果设置t1为分离线程,让main主线程结束,p智能指针析构,进而把Test对象析构,此时show方法已经不会被调用,**因为在threadProc方法中,pw提升到ps时,lock方法判定Test对象已经析构,提升失败!**main函数代码可以如下修改测试:
int main()
{
// 在堆上定义共享对象
shared_ptr<Test> p(new Test);
// 使用C++11的线程,开启一个新线程,并传入共享对象的弱智能指针
std::thread t1(threadProc, weak_ptr<Test>(p));
// 在main线程中析构Test共享对象
// 设置子线程分离
t1.detach();
return 0;
}
该main
函数运行后,最终的threadProc
中,show
方法不会被执行到。以上是在多线程中访问共享对象时,对shared_ptr和weak_ptr的一个典型应用。
自定义删除器
我们经常用智能指针管理的资源是堆内存,当智能指针出作用域的时候,在其析构函数中会delete
释放堆内存资源,但是除了堆内存资源,智能指针还可以管理其它资源,比如打开的文件,此时对于文件指针的关闭,就不能用delete了,这时我们需要自定义智能指针释放资源的方式,先看看unique_ptr智能指针的析构函数代码,如下:
~unique_ptr() noexcept
{ // destroy the object
if (get() != pointer())
{
this->get_deleter()(get()); // 这里获取底层的删除器,进行函数对象的调用
}
}
从unique_ptr
的析构函数可以看到,如果要实现一个自定义的删除器,实际上就是定义一个函数对象而已,示例代码如下:
class FileDeleter
{
public:
// 删除器负责删除资源的函数
void operator()(FILE *pf)
{
fclose(pf);
}
};
int main()
{
// 由于用智能指针管理文件资源,因此传入自定义的删除器类型FileDeleter
unique_ptr<FILE, FileDeleter> filePtr(fopen("data.txt", "w"));
return 0;
}
当然这种方式需要定义额外的函数对象类型,不推荐,可以用C++11提供的函数对象function
和lambda
表达式更好的处理自定义删除器,代码如下:
int main()
{
// 自定义智能指针删除器,关闭文件资源
unique_ptr<FILE, function<void(FILE*)>>
filePtr(fopen("data.txt", "w"), [](FILE *pf)->void{fclose(pf);});
// 自定义智能指针删除器,释放数组资源
unique_ptr<int, function<void(int*)>>
arrayPtr(new int[100], [](int *ptr)->void {delete[]ptr; });
return 0;
}
03 C++11中引入的bind绑定器和function函数对象
1. bind1st 和 bind2nd 什么时候会用到
- C++ STL 中的绑定器
bind1st
:operator()
的第一个形参变量绑定一个确定的值。bind2nd
:operator()
的第二个形参变量绑定一个确定的值。- 绑定器是函数对象的一个应用,绑定器将二元函数对象转化为一元函数对象。
- C++ 11 从 Boost 库中引入了
bind
绑定器和function
函数对象机制。 lambda
表达式:底层依赖函数对象的机制实现的。
2. bind1st 和 bind2nd 的底层实现原理
#include <iostream>
#include <vector>
#include <functional>
#include <algorithm>
#include <ctime>
using namespace std;
using namespace std::placeholders;
template<typename Container>
void showContainer(Container &con)
{
typename Container::iterator it = con.begin();
for (; it != con.end(); ++it)
{
cout << *it << " ";
}
cout << endl;
}
template<typename Iterator, typename Compare>
Iterator my_find_if(Iterator first, Iterator last, Compare comp)
{
for (; first != last; ++first)
{
if (comp(*first))
{
return first;
}
}
return last;
}
template<typename Compare, typename T>
class _mybind1st // 绑定器是函数对象的一个应用
{
public:
_mybind1st(Compare comp, T val)
:_comp(comp), _val(val)
{}
bool operator()(const T &second)
{
return _comp(_val, second);
}
private:
Compare _comp;
T _val;
};
template<typename Compare, typename T>
_mybind1st<Compare, T> mybind1st(Compare comp, const T &val)
{
// 直接使用函数模版,好处是可以进行类型的推演
return _mybind1st<Compare, T>(comp, val);
}
int main()
{
vector<int> vec;
srand(time(nullptr));
for (int i = 0; i < 20; ++i)
{
vec.push_back(rand() % 100 + 1);
}
showContainer(vec);
sort(vec.begin(), vec.end()); // 默认从小到大排序
showContainer(vec);
// greater 二元函数对象
sort(vec.begin(), vec.end(), greater<int>()); // 从大到小排序
showContainer(vec);
int b = 70;
// 把70按顺序插入到vec容器当中
// auto i = find_if(vec.begin(), vec.end(), [b](const int &a) { return a < b; }); // 找到第一个小于70的数字
// vec.insert(i, b);
// auto i = find_if(vec.begin(), vec.end(), bind(greater<int>(), b, _1)); // 绑定器将二元函数对象转化为一元函数对象
// vec.insert(i, b);
// auto i = find_if(vec.begin(), vec.end(), bind1st(greater<int>(), b));
// vec.insert(i, b);
auto i = my_find_if(vec.begin(), vec.end(), mybind1st(greater<int>(), b));
if (i != vec.end())
{
vec.insert(i, 70);
}
showContainer(vec);
return 0;
}
3. function 函数对象类型的应用示例
对于标准库 function
类型
- 用函数类型实例化
function
。 - 通过
function
调用operator()
函数的时候,需要根据函数类型传入相应的参数。
对应C++ Primer P511
4. 模版的完全特例化和部分特例化
#include <iostream>
#include <cstring>
using namespace std;
template<typename T>
class Vector
{
public:
Vector() {cout << "call Vector template init" << endl;}
};
// 下面这个是对char*类型提供的完全特例化版本
template<>
class Vector<char*>
{
public:
Vector() {cout << "call Vector<char*> init" << endl;}
};
// 下面这个是对指针类型提供的部分特例化版本
template<typename Ty>
class Vector<Ty*>
{
public:
Vector() {cout << "call Vector<Ty*> init" << endl;}
};
// 针对函数指针(有一个返回值,有两个形参变量)提供的部分特例化
template<typename R, typename A1, typename A2>
class Vector<R(*)(A1, A2)>
{
public:
Vector() {cout << "call Vector<R(*)(A1, A2)> init" << endl;}
};
// 针对函数类型(有一个返回值,有两个形参变量)提供的部分特例化
template<typename R, typename A1, typename A2>
class Vector<R(A1, A2)>
{
public:
Vector() {cout << "call Vector<R(A1, A2)> init" << endl;}
};
int sum(int a, int b) {return a + b;}
int main()
{
Vector<int> vec1;
Vector<char*> vec2;
Vector<int*> vec3;
Vector<int(*)(int, int)> vec4;
Vector<int(int, int)> vec5;
// 注意区分一下函数类型和函数指针类型
typedef int (*PFUNC1)(int, int);
PFUNC1 pfunc1 = sum;
cout << pfunc1(10, 20) << endl;
typedef int PFUNC2(int, int);
PFUNC2 *pfunc2 = sum;
cout << (*pfunc2)(10, 20) << endl;
return 0;
}
#if 0
template<typename T>
bool compare(T a, T b)
{
cout << "template compare" << endl;
return a > b;
}
template<>
bool compare<const char*>(const char*a, const char*b)
{
cout << "compare<const char*>" << endl;
return strcmp(a, b) > 0;
}
int main()
{
compare(10, 20);
compare("aaa", "bbb");
return 0;
}
#endif
运行结果:
call Vector template init
call Vector<char*> init
call Vector<Ty*> init
call Vector<R(*)(A1, A2)> init
call Vector<R(A1, A2)> init
30
30
5. function 函数对象类型的实现原理
#include <iostream>
#include <string>
#include <functional>
using namespace std;
void hello(string str) {cout << str << endl;}
int sum(int a, int b) {return a + b;}
template<typename Fty>
class myfunction {};
/*
template<typename R, typename A1>
class myfunction<R(A1)>
{
public:
using PFUNC = R(*)(A1);
myfunction(PFUNC pfunc):_pfunc(pfunc){}
R operator()(A1 arg)
{
return _pfunc(arg); // hello(arg)
}
private:
PFUNC _pfunc;
};
template<typename R, typename A1, typename A2>
class myfunction<R(A1, A2)>
{
public:
using PFUNC = R(*)(A1, A2);
myfunction(PFUNC pfunc):_pfunc(pfunc){}
R operator()(A1 arg1, A2 arg2)
{
return _pfunc(arg1, arg2);
}
private:
PFUNC _pfunc;
};
*/
template<typename R, typename... A>
class myfunction<R(A...)>
{
public:
using PFUNC = R(*)(A...);
myfunction(PFUNC pfunc):_pfunc(pfunc){}
R operator()(A... arg)
{
return _pfunc(arg...); // hello(arg)
}
private:
PFUNC _pfunc;
};
int main()
{
myfunction<void(string)> func1(hello);
func1("hello world!"); // func1.operator()("hello world!")
myfunction<int(int, int)> func2(sum);
cout << func2(10, 20) << endl;
return 0;
}
6. bind 和 function 实现线程池
bind
函数可以看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。
调用bind
的一般形式为:auto newCallable = bind(callable, arg_list);
其中,newCallable
本身是一个可调用对象,arg_list
是一个逗号分隔的参数列表,对应给定的callable
的参数。即,当我们调用newCallable
时,newCallable
会调用callable
,并传递给它arg_list
中的参数。
arg_list
中的参数可能包含形如_n
的名字,其中n
是一个整数。这些参数是占位符,表示newCallable
的参数,它们占据了传递给newCallable
的参数的 “位置”。数值n
表示生成的可调用对象中参数的位置:_1
为newCallable
的第一个参数,_2
为第二个参数,依此类推。
记得重看实现线程池的视频
7. lambda 表达式的实现原理
-
可调用对象:对于一个对象或一个表达式,如果可以对其使用调用运算符,则称它为可调用的。
-
C++语言中有几种可调用的对象:函数、函数指针、lambda表达式、bind创建的对象以及重载了函数调用运算符的类。
-
一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。
-
lambda表达式的形式:
[capture list](parameter list)->return type {function body}
-
capture list
(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type
:返回类型;parameter list
:参数列表;function list
:函数体。lambda必须使用尾置返回。 -
我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体。
如果lambda的函数体包含任何单一
return
语句之外的内容,且未指定返回类型,则返回void
。 -
lambda不能有默认参数,一个lambda调用的实参数目与形参数目相等。
-
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。空捕获列表表明lambda不使用它所在函数中的任何局部变量。
-
捕获列表只用于局部非
static
变量,lambda可以直接使用局部static
变量和在它所在函数之外声明的名字。 -
当定义一个lambda时,编译器生成一个与lambda对应的新的未命名的类类型,可以理解为当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象。
默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。
-
变量的捕获方式可以是值或引用。
采用值捕获的前提是变量可以拷贝,被捕获的变量的值在lambda创建时拷贝。
采用引用方式捕获一个变量时,必须保证在lambda执行时变量是存在的。
-
隐式捕获:可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。
在捕获列表中写一个
&
或=
。&
表示编译器采用捕获引用方式,=
表示采用值捕获方式。 -
当混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个
&
或=
,此符号指定了默认捕获方式为引用或值,且显式捕获的变量必须使用与隐式捕获不同的方式。 -
当变量采用值捕获时,如果我们希望能改变一个被捕获的变量的值,就必须在参数列表后加上关键字
mutable
。
8. lambda 表达式的应用实践
既然lambda表达式只能使用在语句当中,如果想跨语句使用之前定义好的lambda表达式,应该如何解决呢?用什么类型来表示lambda表达式?
使用function
类型来表示函数对象的类型,也就可以跨语句使用lambda表达式。
04 C++11知识点汇总
1. C++11常用知识点整理总结
-
关键字和语法
-
auto
:可以根据右值,推导出右值的类型,然后左边变量的类型也就已知了。 -
nullptr
:给指针专用(能够和整数进行区分) -
foreach
:可以遍历数组、容器等,底层就是通过指针或者迭代器来实现的,也叫作范围遍历。for (auto val: container) { cout << val << " "; }
-
右值引用:
move
移动语义函数和forward
类型完美转发函数 -
模版的一个新特性:
typename... A
表示可变参(类型参数)
-
-
绑定器和函数对象
function
:函数对象bind
:绑定器lambda
表达式
-
智能指针
shared_ptr
和weak_ptr
、unique_ptr
-
容器
unordered_set
和unordered_map
:底层是哈希表,O(1)
array
:数组forward_list
:前向链表
-
C++语言级别的多线程编程:代码可以跨平台
thread
、mutex
、condition_variable
、lock_guard
、unique_lock
、sleep_for
atomic
原子类型,基于CAS
操作的原子类型,线程安全的
2. 通过 thread 类编写 C++ 多线程程序
2.1、怎么创建启动一个线程?
std::thread
定义一个线程对象,传入线程所需要的线程函数和参数,线程自动开启。
2.2、子线程如何结束?
子线程函数运行完成,线程就结束了。
2.3、主线程如何处理子线程呢?
假设主线程中处理子线程t
,有两种处理方式:
-
在主线程中调用
t.join()
:主线程等待子线程t
结束,主线程继续往下运行。 -
在主线程中调用
t.detach()
:把子线程t
设置为分离线程,主线程结束,整个进程结束,所有子线程都自动结束了。
#include <iostream>
#include <thread>
using namespace std;
void threadHandle1(int time)
{
// 让子线程睡眠time秒
std::this_thread::sleep_for(std::chrono::seconds(time));
cout << "hello thread1!" << endl;
}
void threadHandle2(int time)
{
// 让子线程睡眠time秒
std::this_thread::sleep_for(std::chrono::seconds(time));
cout << "hello thread2!" << endl;
}
int main()
{
// 创建了一个线程对象,传入一个线程函数,新线程就开始运行了
std::thread t1(threadHandle1, 2);
std::thread t2(threadHandle2, 3);
// 主线程等待子线程结束,主线程继续往下运行
t1.join();
t2.join();
// 把子线程设置为分离线程
// t1.detach();
cout << "main thread done!" << endl;
// 主线程运行完成,如果当前进程还有未运行完成的子线程,进程就会异常终止。
return 0;
}
3. 线程间互斥:mutex 互斥锁和 lock_guard
多线程程序执行的结果应是一致的,不会随着CPU对线程不同的调用顺序,而产生不同的运行结果。如果每次执行的结果不一致,则说明多线程存在 竞态条件。
C++ thread 模拟车站三个窗口卖票的程序
#include <iostream>
#include <thread>
#include <mutex>
#include <list>
using namespace std;
/*
C++ thread 模拟车站三个窗口卖票的程序
线程间的互斥,需要用到互斥锁mutex,为了防止解锁语句未运行而发生死锁,可以使用lock_guard封装mutex。
*/
int ticketCount = 100; // 车站有100张车票,由三个窗口一起卖票
std::mutex mtx; // 全局的一把互斥锁
// 模拟卖票的线程函数
void sellTicket(int index)
{
while (ticketCount > 0) // 锁 + 双重判断
{
// mtx.lock();
{
lock_guard<std::mutex> lock(mtx); // 保证所有线程都能释放锁,防止死锁问题的发生
if (ticketCount > 0)
{
// 临界区代码段,需要保证为原子操作,线程间互斥,使用mutex
cout << "窗口:" << index << "卖出第:" << ticketCount << "张票!" << endl;
ticketCount--;
}
}
// mtx.unlock();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main()
{
list<std::thread> tlist;
for (int i = 1; i <= 3; ++i)
{
tlist.push_back(std::thread(sellTicket, i));
}
for (std::thread &t: tlist)
{
t.join();
}
cout << "所有窗口卖票结束!" << endl;
return 0;
}
4. 线程间的同步通信机制:生产者消费者模型
多线程编程的两个问题:
-
线程间的互斥
竞态条件 =》临界区代码段=》保证原子操作 =》互斥锁
mutex
、轻量级的无锁实现CAS
-
线程间的同步通信
生产者消费者模型
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue> // C++ STL所有的容器都不是线程安全的
using namespace std;
std::mutex mtx; // 定义互斥锁,做线程间的互斥操作
std::condition_variable cv; // 定义条件变量,做线程间的同步通信操作
// 生产者生产一个物品,通知消费者消费一个物品;消费完了,消费者再通知生产者继续生产物品
class Queue
{
public:
void put(int val) // 生产物品
{
unique_lock<std::mutex> lck(mtx);
while (!que.empty())
{
// que不为空,生产者应该通知消费者去消费物品,消费完了,再继续生产
// 生产者线程进入等待状态,并且把mtx互斥锁释放掉
cv.wait(lck);
}
que.push(val);
/*
notify_one:通知另外的一个线程消费物品
notify_all:通知其他所有的线程消费物品
通知其他所有的线程,我生产了一个物品,你们赶紧去消费吧
其他线程得到通知,就会从等待状态变为阻塞状态,在获取到互斥锁后才能继续执行
*/
cv.notify_all();
cout << "生产者 生产:" << val << "号物品" << endl;
}
int get() // 消费物品
{
unique_lock<std::mutex> lck(mtx);
while (que.empty())
{
// 消费者线程发现que是空的,通知生产者线程生产物品
// 消费者线程进入等待状态,并且把mtx互斥锁释放掉
cv.wait(lck);
}
int val = que.front();
que.pop();
cv.notify_all(); // 通知其他线程我消费完了,赶紧生产吧
cout << "消费者 消费:" << val << "号物品" << endl;
return val;
}
private:
queue<int> que;
};
void producer(Queue *que) // 生产者线程
{
for (int i = 1; i <= 10; ++i)
{
que->put(i);
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
void consumer(Queue *que) // 消费者线程
{
for (int i = 1; i <= 10; ++i)
{
que->get();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
}
int main()
{
Queue que; // 两个线程共享的队列
std::thread t1(producer, &que);
std::thread t2(consumer, &que);
t1.join();
t2.join();
return 0;
}
5. 再谈 lock_guard 和 unique_lock
lock_guard
不提供拷贝构造函数和赋值重载函数,因此它不能用在函数参数传递以及返回过程中,只能使用在简单的临界区代码段的互斥操作中。unique_lock
提供了右值引用参数的拷贝构造和赋值重载函数,因此它不仅可以使用在简单的临界区代码段的互斥操作中,还能用在函数调用过程中。
6. 基于 CAS 操作的 atomic 原子类型
轻量级的无锁实现CAS
,是在硬件层面系统总线实现的加锁和解锁操作。
#include <iostream>
#include <thread>
#include <atomic>
#include <list>
using namespace std;
// 使用volatile关键字修饰变量,编译器不会对变量进行优化,也就不会在CPU中对变量进行缓存,
// 每次使用变量时都从内存中取值,可以确保在多线程环境下正确地进行同步。
volatile std::atomic_bool isReady = false;
volatile std::atomic_int mycount = 0;
void task()
{
while (!isReady)
{
std::this_thread::yield(); // 线程出让当前的CPU时间片,等待下一次调度
}
for (int i = 0; i < 100; ++i)
{
mycount++;
}
}
int main()
{
list<std::thread> tlist;
for (int i = 0; i < 10; ++i)
{
tlist.push_back(std::thread(task));
}
std::this_thread::sleep_for(std::chrono::seconds(3));
isReady = true;
for (std::thread &t : tlist)
{
t.join();
}
cout << "mycount:" << mycount << endl;
return 0;
}
05 设计模式
1. 单例模式(属于创建型设计模式)
单例模式:一个类不管创建多少对象,永远只能得到该类型一个对象的实例。常用于日志模块、数据库模块等。
单例模式分为饿汉式单例模式和懒汉式单例模式
-
饿汉式单例模式:在还没有获取实例对象之前,实例对象就已经产生了。
-
懒汉式单例模式:直到第一次获取实例对象时,唯一的实例对象才产生。
饿汉式单例模式代码
#include <iostream>
using namespace std;
// 饿汉式单例模式 一定是线程安全的
class Singleton
{
public:
static Singleton* getInstance() // 3、获取类的唯一实例对象的接口方法
{
return &instance;
}
private:
static Singleton instance; // 2、定义类的唯一实例对象
Singleton() // 1、构造函数私有化
{
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton Singleton::instance;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
return 0;
}
懒汉式单例模式代码1
#include <iostream>
#include <mutex>
using namespace std;
std::mutex mtx;
// 线程安全的懒汉式单例模式
class Singleton
{
public:
static Singleton* getInstance() // 2、获取类的唯一实例对象的接口方法
{
if (instance == nullptr)
{
lock_guard<std::mutex> guard(mtx);
if (instance == nullptr) // 锁+双重判断
{
// 开辟内存、构造对象、给instance赋值
instance = new Singleton(); // 3、定义类的唯一实例对象
}
}
return instance;
}
private:
static Singleton *volatile instance;
Singleton() // 1、构造函数私有化
{
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
Singleton*volatile Singleton::instance = nullptr;
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
return 0;
}
懒汉式单例模式代码2
#include <iostream>
#include <mutex>
using namespace std;
// 线程安全的懒汉式单例模式
class Singleton
{
public:
static Singleton* getInstance() // 2、获取类的唯一实例对象的接口方法
{
// 函数静态局部变量的初始化,在汇编指令上已经自动添加线程互斥指令了,因此不用担心线程安全的问题。
static Singleton instance; // 3、定义类的唯一实例对象
return &instance;
}
private:
Singleton() // 1、构造函数私有化
{
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
int main()
{
Singleton *p1 = Singleton::getInstance();
Singleton *p2 = Singleton::getInstance();
Singleton *p3 = Singleton::getInstance();
return 0;
}
2. 工厂模式(属于创建型设计模式)
工厂模式:主要是封装了对象的创建。
工厂模式分为:
-
简单工厂(
Simple Factory
)把对象的创建封装在一个接口函数里面,通过传入不同的标识,返回创建的对象。
优点:客户不用自己负责
new
对象,不用了解对象创建的详细过程。缺点:提供创建对象实例的接口函数不闭合,不能对修改关闭。
-
工厂方法(
Factory Method
)Factory
基类,提供了一个纯虚函数(创建产品),定义派生类(具体产品的工厂)负责创建对应的产品。优点:可以做到不同的产品在不同的工厂里面创建,能够对现有工厂以及产品的修改关闭。
缺点:实际上,很多产品是有关联关系的,属于一个产品簇,不应该放在不同的工厂里面去创建,这样一是不符合实际的产品对象创建逻辑,二是工厂类太多了,不好维护。
-
抽象工厂(
Abstract Factory
)把有关联关系的,属于一个产品簇的所有产品创建的接口函数,放在一个抽象工厂里面
AbstractFactory
,派生类(具体产品的工厂)应该负责创建该产品簇里面所有的产品。相比于工厂方法,这样做可以减少大量工厂类的创建,同时也更加符合实际的产品对象创建逻辑。
简单工厂
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Car
{
public:
Car(string name) :_name(name) {}
virtual void show() = 0;
protected:
string _name;
};
class Bmw : public Car
{
public:
Bmw(string name) : Car(name) {}
void show()
{
cout << "获取了一辆宝马汽车:" << _name << endl;
}
};
class Audi : public Car
{
public:
Audi(string name) : Car(name) {}
void show()
{
cout << "获取了一辆奥迪汽车:" << _name << endl;
}
};
// 简单工厂
enum CarType
{
BMW, AUDI
};
class SimpleFactory
{
public:
Car* createCar(CarType ct) // 不符合软件设计的“开闭原则”:对拓展开放,对修改关闭
{
switch (ct)
{
case BMW:
return new Bmw("X5");
case AUDI:
return new Audi("A6");
default:
cerr << "传入工厂的参数不正确:" << ct << endl;
break;
}
return nullptr;
}
};
int main()
{
// Car *p1 = new Bmw("X5");
// Car *p2 = new Audi("A6");
unique_ptr<SimpleFactory> factory(new SimpleFactory());
unique_ptr<Car> p1(factory->createCar(BMW));
unique_ptr<Car> p2(factory->createCar(AUDI));
p1->show();
p2->show();
return 0;
}
工厂方法
#include <iostream>
#include <string>
#include <memory>
using namespace std;
class Car
{
public:
Car(string name) :_name(name) {}
virtual void show() = 0;
protected:
string _name;
};
class Bmw : public Car
{
public:
Bmw(string name) : Car(name) {}
void show()
{
cout << "获取了一辆宝马汽车:" << _name << endl;
}
};
class Audi : public Car
{
public:
Audi(string name) : Car(name) {}
void show()
{
cout << "获取了一辆奥迪汽车:" << _name << endl;
}
};
// 工厂方法
class Factory
{
public:
virtual Car* createCar(string name) = 0; // 工厂方法
};
// 宝马工厂
class BmwFactory : public Factory
{
public:
Car* createCar(string name)
{
return new Bmw(name);
}
};
// 奥迪工厂
class AudiFactory : public Factory
{
public:
Car* createCar(string name)
{
return new Audi(name);
}
};
int main()
{
// Car *p1 = new Bmw("X5");
// Car *p2 = new Audi("A6");
unique_ptr<Factory> bmwfty(new BmwFactory());
unique_ptr<Factory> audifty(new AudiFactory());
unique_ptr<Car> p1(bmwfty->createCar("X6"));
unique_ptr<Car> p2(audifty->createCar("A8"));
p1->show();
p2->show();
return 0;
}
抽象工厂
#include <iostream>
#include <string>
#include <memory>
using namespace std;
// 系列产品1
class Car
{
public:
Car(string name) :_name(name) {}
virtual void show() = 0;
protected:
string _name;
};
class Bmw : public Car
{
public:
Bmw(string name) : Car(name) {}
void show()
{
cout << "获取了一辆宝马汽车:" << _name << endl;
}
};
class Audi : public Car
{
public:
Audi(string name) : Car(name) {}
void show()
{
cout << "获取了一辆奥迪汽车:" << _name << endl;
}
};
// 系列产品2
class CarLight
{
public:
virtual void show() = 0;
};
class BmwLight : public CarLight
{
public:
void show() {cout << "Bmw light!" << endl;}
};
class AudiLight : public CarLight
{
public:
void show() {cout << "Audi light!" << endl;}
};
// 抽象工厂(对一组有关联关系的产品簇提供产品对象的统一创建)
class AbstractFactory
{
public:
virtual Car* createCar(string name) = 0; // 工厂方法 创建汽车
virtual CarLight* createCarLight() = 0; // 工厂方法 创建汽车关联的产品,车灯
};
// 宝马工厂
class BmwFactory : public AbstractFactory
{
public:
Car* createCar(string name)
{
return new Bmw(name);
}
CarLight* createCarLight()
{
return new BmwLight();
}
};
// 奥迪工厂
class AudiFactory : public AbstractFactory
{
public:
Car* createCar(string name)
{
return new Audi(name);
}
CarLight* createCarLight()
{
return new AudiLight();
}
};
int main()
{
// Car *p1 = new Bmw("X5");
// Car *p2 = new Audi("A6");
unique_ptr<AbstractFactory> bmwfty(new BmwFactory());
unique_ptr<AbstractFactory> audifty(new AudiFactory());
unique_ptr<Car> p1(bmwfty->createCar("X6"));
unique_ptr<Car> p2(audifty->createCar("A8"));
unique_ptr<CarLight> l1(bmwfty->createCarLight());
unique_ptr<CarLight> l2(audifty->createCarLight());
p1->show();
l1->show();
p2->show();
l2->show();
return 0;
}
运行结果:
获取了一辆宝马汽车:X6
Bmw light!
获取了一辆奥迪汽车:A8
Audi light!
3. 代理模式(属于结构型设计模式)
结构型模式:这些设计模式关注类和对象的组合。继承的概念被用来组合接口和定义组合对象获得新功能的方式。
代理模式(Proxy Pattern
):通过代理类,来控制实际对象的访问权限。
#include <iostream>
#include <memory>
using namespace std;
class VideoSite // 抽象类
{
public:
virtual void freeMovie() = 0; // 免费电影
virtual void vipMovie() = 0; // vip电影
virtual void ticketMovie() = 0; // 用券观看电影
};
// 委托类
class FixBugVideoSite : public VideoSite
{
public:
virtual void freeMovie() // 免费电影
{
cout << "观看免费电影" << endl;
}
virtual void vipMovie() // vip电影
{
cout << "观看VIP电影" << endl;
}
virtual void ticketMovie() // 用券观看电影
{
cout << "用券观看电影" << endl;
}
};
// 代理类 代理FixBugVideoSite
class FreeVideoSite : public VideoSite
{
public:
FreeVideoSite() { pVideo = new FixBugVideoSite(); }
~FreeVideoSite() { delete pVideo; }
void freeMovie() // 免费电影
{
pVideo->freeMovie(); // 通过代理对象的freeMovie,来访问真正委托类对象的freeMovie方法
}
void vipMovie() // vip电影
{
cout << "您现在是普通用户,升级为VIP会员即可观看!" << endl;
}
void ticketMovie() // 用券观看电影
{
cout << "您目前没有券,购买电影券即可观看!" << endl;
}
private:
VideoSite* pVideo;
};
// 代理类 代理FixBugVideoSite
class VipVideoSite : public VideoSite
{
public:
VipVideoSite() { pVideo = new FixBugVideoSite(); }
~VipVideoSite() { delete pVideo; }
void freeMovie() // 免费电影
{
pVideo->freeMovie(); // 通过代理对象的freeMovie,来访问真正委托类对象的freeMovie方法
}
void vipMovie() // vip电影
{
pVideo->vipMovie();
}
void ticketMovie() // 用券观看电影
{
cout << "您目前没有券,购买电影券即可观看!" << endl;
}
private:
VideoSite* pVideo;
};
void watchMovie(unique_ptr<VideoSite> &ptr)
{
ptr->freeMovie();
ptr->vipMovie();
ptr->ticketMovie();
}
int main()
{
unique_ptr<VideoSite> p1(new FreeVideoSite());
unique_ptr<VideoSite> p2(new VipVideoSite());
watchMovie(p1);
watchMovie(p2);
return 0;
}
运行结果:
观看免费电影
您现在是普通用户,升级为VIP会员即可观看!
您目前没有券,购买电影券即可观看!
观看免费电影
观看VIP电影
您目前没有券,购买电影券即可观看!
4. 装饰器模式(属于结构型设计模式)
装饰器模式(Decorator Pattern
):主要是增加现有类的功能,但是增加现有类的功能还有一个方法就是新增加一个子类。
通过子类实现功能增强的问题:为了增强现有类的功能,通过实现子类的方式,重写接口,是可以完成功能拓展的,但是有太多的子类被添加到代码中。
#include <iostream>
#include <memory>
using namespace std;
class Car // 抽象基类
{
public:
virtual void show() = 0;
};
// 三个汽车实体类
class Bmw : public Car
{
public:
void show()
{
cout << "这是一辆宝马汽车,配置有:基类配置";
}
};
class Audi : public Car
{
public:
void show()
{
cout << "这是一辆奥迪汽车,配置有:基类配置";
}
};
class Benz : public Car
{
public:
void show()
{
cout << "这是一辆奔驰汽车,配置有:基类配置";
}
};
// 装饰器1 定速巡航
class ConcreteDecorator01 : public Car
{
public:
ConcreteDecorator01(Car *p):pCar(p){}
void show()
{
pCar->show();
cout << ",定速巡航";
}
private:
Car *pCar;
};
// 装饰器2 自动刹车
class ConcreteDecorator02 : public Car
{
public:
ConcreteDecorator02(Car *p):pCar(p){}
void show()
{
pCar->show();
cout << ",自动刹车";
}
private:
Car *pCar;
};
// 装饰器3 车道偏离
class ConcreteDecorator03 : public Car
{
public:
ConcreteDecorator03(Car *p):pCar(p){}
void show()
{
pCar->show();
cout << ",车道偏离";
}
private:
Car *pCar;
};
int main()
{
Car *p1 = new ConcreteDecorator01(new Bmw());
p1 = new ConcreteDecorator02(p1);
p1 = new ConcreteDecorator03(p1);
p1->show();
cout << endl;
Car *p2 = new ConcreteDecorator01(new Audi());
p2->show();
cout << endl;
Car *p3 = new ConcreteDecorator01(new Benz());
p3->show();
cout << endl;
return 0;
}
5. 适配器模式(属于结构型设计模式)
适配器模式:让不兼容的接口可以在一起工作。
#include <iostream>
using namespace std;
// VGA接口类
class VGA
{
public:
virtual void play() = 0;
};
// TV01表示支持VGA接口的投影仪
class TV01 : public VGA
{
public:
void play()
{
cout << "通过VGA接口连接投影仪,进行视频播放" << endl;
}
};
// 实现一个电脑类(只支持VGA接口)
class Computer
{
public:
// 由于电脑只支持VGA接口,所以该方法的参数也只能支持VGA接口的指针或引用
void playVideo(VGA *pVGA)
{
pVGA->play();
}
};
/*
现在进了一批新的投影仪,但是新的投影仪都只支持HDMI接口,而电脑又只支持VGA接口,该如何解决接口不兼容的问题呢?
方法1:换一个支持HDMI接口的电脑,这个叫代码重构。
方法2:买一个转换头(适配器),能够把VGA信号转成HDMI信号,这个叫添加适配器类。
*/
// 进了一批新的投影仪,但是新的投影仪都只支持HDMI接口
class HDMI
{
public:
virtual void play() = 0;
};
// TV02表示支持HDMI接口的投影仪
class TV02 : public HDMI
{
public:
void play()
{
cout << "通过HDMI接口连接投影仪,进行视频播放" << endl;
}
};
// 由于电脑(VGA接口)和投影仪(HDMI接口)无法直接相连,所以需要添加适配器类
class VGAToHDMIAdapter : public VGA
{
public:
VGAToHDMIAdapter(HDMI *p) :pHdmi(p) {}
void play() // 该方法相当于转换头,做不同接口的信号转换的
{
pHdmi->play();
}
private:
HDMI *pHdmi;
};
int main()
{
Computer comp;
comp.playVideo(new TV01());
comp.playVideo(new VGAToHDMIAdapter(new TV02()));
return 0;
}
6. 观察者模式(属于行为型设计模式)
-
行为型设计模式:主要关注的是对象之间的通信。
-
观察者设计模式(Observer Pattern)也称作观察者-监听者模式、发布-订阅模式。
-
观察者模式:主要关注的是对象的一对多的关系,也就是多个对象都依赖一个对象,当该对象的状态发生改变时,其他对象都能够接收到相应的通知。
比如:有一组数据(数据对象),通过这一组数据对象可以绘制出曲线图(对象1)、柱状图(对象2)、圆饼图(对象3),当数据对象改变时,对象1、对象2、对象3应该及时的收到相应的通知!
现在有个主题Subject
和三个观察者Observer1
、Observer2
、Observer3
,假设主题有更改,应该及时通知相应的观察者,去处理相应的事件。
#include <iostream>
#include <string>
#include <unordered_map>
#include <list>
using namespace std;
// 观察者抽象类
class Observer
{
public:
// 处理消息的接口
virtual void handle(int msgid) = 0;
};
// 第一个观察者实例
class Observer1 : public Observer
{
public:
void handle(int msgid)
{
switch (msgid)
{
case 1:
cout << "Observer1 recv 1 msg!" << endl;
break;
case 2:
cout << "Observer1 recv 2 msg!" << endl;
break;
default:
cout << "Observer1 recv unknown msg!" << endl;
break;
}
}
};
// 第二个观察者实例
class Observer2 : public Observer
{
public:
void handle(int msgid)
{
switch (msgid)
{
case 2:
cout << "Observer2 recv 2 msg!" << endl;
break;
default:
cout << "Observer2 recv unknown msg!" << endl;
break;
}
}
};
// 第三个观察者实例
class Observer3 : public Observer
{
public:
void handle(int msgid)
{
switch (msgid)
{
case 1:
cout << "Observer3 recv 1 msg!" << endl;
break;
case 3:
cout << "Observer3 recv 3 msg!" << endl;
break;
default:
cout << "Observer3 recv unknown msg!" << endl;
break;
}
}
};
// 主题类
class Subject
{
public:
// 给主题增加观察者对象
void addObserver(Observer* obser, int msgid)
{
_subMap[msgid].push_back(obser);
}
// 主题检测发生改变,通知相应的观察者对象处理事件
void dispatch(int msgid)
{
auto it = _subMap.find(msgid);
if (it != _subMap.end())
{
for (Observer *pObser : it->second)
{
pObser->handle(msgid);
}
}
}
private:
unordered_map<int, list<Observer*>> _subMap;
};
int main()
{
Subject subject;
Observer *p1 = new Observer1();
Observer *p2 = new Observer2();
Observer *p3 = new Observer3();
subject.addObserver(p1, 1);
subject.addObserver(p1, 2);
subject.addObserver(p2, 2);
subject.addObserver(p3, 1);
subject.addObserver(p3, 3);
int msgid = 0;
while (1)
{
cout << "请输入id:";
cin >> msgid;
if (msgid == -1)
break;
subject.dispatch(msgid);
}
return 0;
}