学习AA大神c++设计新思维笔记:0607单件与灵针
单件
台湾人的,实作是实现的意思.
很复杂的.
包括如何分配,如何消灭,多线程安全,消灭后操作.
静态函数/成员变量的缺点:不能为虚函数,外界很难改变行为.并且很难初化/清理.
元<型名 T,
元<类>类 创建策略=用新创建,
元<类>类 生命期策略=默认生命期,
元<类,类>类 线程模型=洛基默认线程无对象级,
类 互斥锁策略=洛基默认互斥锁
>
类 单件持有者{
公:
又 T 对象类型;
静 T&实例();
私:
静 空 造实例();
静 空 洛基限定c调用传统 消灭单件();
单件持有者();
又 型名 线程模型<T*,互斥锁策略>::易失类型 针实例类型;
静 针实例类型 p实例_;
静 极 消灭_;
};
//这样实例化.
{
如(!p实例_){造实例();}
中*p实例_;//静态初化.
}//传回引用较安全,避免被`删`.
将拷贝/复制构造声明为私/删.禁用.析构函数声明为私.用消灭函数来消灭.
消灭时或者利用局部静态变量.要区别执行期初化与编译期常量初化(运行前就完成赋值).利用的是退出时().c运行时程序结束时运行.
单件模式特征:易于表达/理解,但难以实现.
局部静态单件,不一定正确.如键盘,显示,日志.
键盘成功,显示失败,则日志跟随失败.则是死引用.因为日志也是单件.此时键盘再失败,则无法记录.因为日志已被消灭了.希望日志无论何时创建,但得在(键盘,显示)之后消灭.一个函数一个职责.现在要增加检测死引用,负责处理错误.增加一个已消灭变量.如果寿命更长对象取这个单件,则到达死引用时,并抛异常.
进一步,不死鸟来了.检测死引用时复活.因为静态变量内存在整个程序周期都存在.在原地复活.新(针)单件.然后在退出时(注册消灭)
退出时修复.修复退出时问题.要定义#修复退出时.
不然,要出问题.
死引用.带寿命的单件.不死鸟,如有状态,则可能未保存状态.
生命期控制.现在的rust就有.置寿命,感觉就是自己当析构管家,管理析构顺序,只针对用新分配的对象.
或者进行依赖管理.但也麻烦,会产生循环依赖.
寿命法则:A依赖B,则A比B短命.所以不要依赖.
带寿命单件,用类似优先队,但寿命值相同时,先进后出.
多线程更麻烦.双检测是错误的解法.锁很贵,最后可能得靠原子.
空 单件持有者<T,创建策略,
生命期策略,线程模型,互斥锁策略>::造实例()
{
型名 线程模型<单件持有者,互斥锁策略>::锁 警卫;
(空)警卫;
如(!p实例_)
{
如(消灭_)
{
消灭_=假;
生命期策略<T>::死引用时();
}//死引用时
p实例_=创建策略<T>::创建();
生命期策略<T>::计划析构(p实例_,
&消灭单件);
}
}
灵针
现在已有独针,共针,弱针.具有两个操作->与*操作.灵针具有值语义(即可以复制),而指针没有.
有移动,共享(引用计数),总是复制指针,
拥有权与构造/复制/析构关系很大.
不要写死代码.降低通用性.如处理非标__near,__far,__huge时.如果有别人的灵针想对其升级时,将这个灵针包装在自己的灵针中,而不是继承他.
当调用->时,类型非内置类型,编译器不断->直到达到内置指针,再访问成员.因而灵针可以不传指针,而传有->方法的对象
前调用,后调用,假定按值从->操作返回一个指针对象,然后:
1,构造指针对象.
2,调用指针对象的->,返回被指对象指针.
3,访问被指对象的->,执行实际动作.
4,析构指针对象.
在多线程及访问资源时有用,叫锁定函数调用,可在构造函数中锁定资源,析构函数中解除锁定.
句柄防止用户直接操作资源.是隐藏表格的指针索引此时,提供->/*操作没意义.
因而,提出被指类型,存储类型,引用类型三种存储类型作为一种策略.灵针不适合用成员函数.因为会混淆灵针/被指对象的调用.
如打印机也有获取/释放.光靠->/.来区别是不现实的.因而灵针只使用非成员函数,通过友来解决.
灵针需要保留,构造,析构,->,=,*,函数,其余由命名非成员函数提供.
拥有权管理最重要.如移动,共享.
一种是,只管复制,比按值传递多的好处就是多态.深复制,用虚复制.具体复制时,返回子对象的基针,靠策略.
一是写时复制.但这个写时复制无法区分被指对象的常与非常成员函数调用.灵针太低级,写时复制很高级了.不过,实现写时复制时,可以用灵针.
引用计数,最普遍,最有效.这些都是小对象,最好用小对象分配器不然,分配太慢,如果通过间接层,则访问时又太慢.最好将计数通过被指对象保存起来.这是侵入式计数.非侵入式,最好也用小对象分配器.
还有引用链,双向引用链,插入,删除,空检测都是常数时间.引用链空间花费少,时间多.一般不用.
引用计数还有个问题,就是循环引用,这就是弱针上场时.
移动指针.转移所有权.还可以重载&操作符.取址.但不好.暴露了地址,放弃了自动管理.且不能使用stl了.因为多数代码假定用&返回T*.还可以隐式转为原始指针,可以通过显式转换使用.如.取()得到原始指针.
采取完整可靠操作,重载每个操作符.
还有,就是灵针<基>与灵针<继>之间的转换.重载==与!=为相应的模板类.
元<型名 T1,元<类>类 操作1,类 CP1,元<类>类 KP1,元<类>类 SP1,元<类>类 CNP1>
极 符号==(常 灵巧针<T1,操作1,CP1,KP1,SP1,CNP1>&右边)常
{中 取实现(*本)==取实现(右边);}
由用户来实现符号<以达到排序功能.借助标::较小可以实现.同一程序,可能需要高安/低速或高速/低安.
检测分初化检测/提前检测.如不能为空针,但有时空针又有用.解引用时要检查,检查一次后,以后就不检查了.安全/高效.再一个是错误报告/错误策略,
双常性,指针的常,被指的常.
数组.如果灵针<数组>,则需要记得删[],此数组非现在数组.
多线程.被指对象锁定,加个间接层,构造函数中加个锁.借助->不断的->.访问后就析构间接层,释放锁了.
双链结构,都得加锁.将多线程加入所有者策略中.
类级锁定锁定某个类的所有对象.对象级锁定,只锁定对象相关操作.前者慢,占用内存少.后者快,占用内存多.多线程安全的引用计数,需要原子操作,多线程的引用链接则需要锁.原子操作相对来说,要便宜一点.
每一个可变点转为策略.
组合起来:存储,所有者,转换,检查策略.
把一个策略当作大函数,则什么都明白了.策略类直接作为模板的参数.一般,最常定制的策略放在最前.所有策略要具值语义:(复制构造/赋值)函数.
浙公网安备 33010602011771号