Effective C++学习第八天

条款26:尽可能延后变量定义式的出现时间

          当你定义了一个变量,如果在使用变量之前出现异常,那么你得承受一次构造成本和析构成本,而且你没有使用该变量;本条款给出的建议是延迟变量的定义,直到非得使用该变量的前一刻为止,甚至应该尝试延后这份定义知道能够给它初值实参为止;这样不仅可以避免构造(析构)非必要的对象,还可以避免无意义的default构造行为;

条款27:尽量少做转型动作

          常见C风格的类型转换:(T)expression                 函数风格的转型   T(expression)      //旧式转型

          C++提供的四种新式转型:

                const_cast<T>(expression);//去除对象常量性;

                dynamic_cast<T>(expression);//对象安全向下转型,用于继承

                reinterpret_cast<T>(expression);

                static_cast<T>(expression);//隐式转型

尽量使用新式转型方式:1)代码容易辨识;2)各转型动作的目标比较窄,编译器容易诊断;

          转型并不是告诉编译器把某种类型视为另一种类型,任何一个类型转换(不论是通过转型操作而进行的显式转换或通过编译器完成的隐式转换)往往真的令编译器编译出运行期间执行的代码;

          对于一个base class指针指向一个derived class对象,有时候上述的两个指针值并不相同,它们之间会有一个偏移量在运行期间施加在derived*指针上来得到正确的base*指针值;转型代码错误分析:

class window{                                       class specialwindow:public window{

public:                                               public:

           virtual void onresize( ){...}                              virtual void onresize( ){

}                                                                                 static_cast<window>(*this).onresize( );//解决方法

                                                                                                       //window::onresize();

                                                                                 }

原本想着采用static_cast将派生类对象转化为基类对象执行onresize函数,然后再执行派生类的onresize函数;但实际上是在当前对象的副本上执行了window::onresize,在当前对象上执行是specialwindow专属动作(个人理解:相当于转型可以当做为创建一个副本执行了一次函数调用的过程);

对于dynamic_cast:用于你相对一个derived class执行derived class操作函数,但是你的手上只有一个指向base的pointer或者reference,你只能通过dynamic_cast向下寻找你的derived class对象,然而使用dynamic_cast会用到strcmp比较class名称,使得运行效率极低;改善这个问题的方法有两种:

          1)使用容器并在其中存储直接指向derived class对象指针(通常是智能指针),如此便消除了通过base class接口处理对象的需要(但这种情况下,你需要多个容器分别储存不同派生类的指针,而且每个容器都具备类型安全性),具体代码如下:

class window {...};

class specialwindow::public window{

public:

               void blink( );

};

typedef std::vector<std::trl:shared_ptr<specialwindow>vpsw;

vpsw winptrs;

for(vpsw::iterator iter=winptrs.begin( );iter!=winptrs.end( );iter++)

                  (*it)->blink( );

            2)通过base class接口处理所有可能之各种window派生类,也就是提供virtual函数;

条款28:避免返回handles指向对象内部成分

           对于常见的pimpl设计方法(即数据和实现分离),有两个需要注意的地方:1)成员变量的封装性最多等于返回你reference的函数的访问级别;2)const成员函数传出来一个reference,后者(reference)所指数据与对象自身有关联,而它又被储存于对象之外,那么函数调用者可以修改那笔数据;

           绝对不要令成员函数返回一个指针指向访问级别较低的成员函数,如果你那么做,后者的访问级别就会被提高到较高者,因为客户可以去的一个指向访问级别更低的函数,然后通过那个指针去调用它;

           如果有一个handle(成员函数返回reference,指针或者迭代器)被传出去,那么就可以用这个handle访问对象的数据,对象的封装性也就下降了;

         避免返回handles(reference,指针,迭代器)指向对象内部,遵守这个规定,可以增加封装性,帮助const成员函数更像const,并将发生“虚吊号码牌(指针或引用指向不存在的对象)”的可能性降到最低;

条款29:为“异常安全”而努力是值得的

         当异常抛出时,带有异常安全性的函数会:1)不泄露资源;2)不允许数据败坏;

         异常安全函数提供一下三个保证中的一个:1)基本承诺;异常被抛出,程序内的任何事情仍然保持在有效的状态下,没有任何对象或者数据结构会因此而败坏,所有对象都处于一种内部前后一致的状态;2)强烈保证:如果异常被抛出,程序状态不改变。调用这样的函数需有这样的认知,如果函数成功,就是完全成功,如果函数失败,程序会回复到“调用函数之前的状态”,调用一个提供强烈保证的函数后,程序状态只有两种可能,如预期般到达函数成功执行后的状态或者回到函数被调用前的状态。3)不抛掷保证,承诺不抛掷异常,因为它们总能完成它们原先承诺的功能;

         强烈保证往往能够以copy-and-swap实现出来,但是强烈保证并非对所有的函数都有实现或者具备现实意义,一般基本承诺就可以了;

       函数提供的“异常安全性”通常最高只等于其所调用之各个函数的“异常安全性保证”中的最弱者。

条款31:透彻了解inlining的里里外外

           inlining函数的好处:产生较小的目标码,调用它们不需要承受函数调用所招致的额外开销;缺点是会导致程序体积过大(虚内存现象)inline造成代码膨胀导致额外的换页行为,降低高速缓存器装置的击中率; 

         inline只是对编译器的一个申请,不是强制命令,编译器可以忽略;这项申请可以隐喻提出,也可以明确指出(加inline关键字)。隐喻的方式是将函数定义在class定义式内;

        inline函数通常被放置在头文件中,在编译阶段实现对函数本体的替换;编译器拒绝将复杂(循环或者递归)、virtual函数函数指针调用的情况下将函数变成inline;

        inline函数在随着程序库的升级时无法升级,同时不支持设置端点调试(本身不是函数);

       结论:将大多数inline函数限制在小型、被频繁调用的函数身上,这可以使得日后调试过程和二进制升级更容易,也可以使得潜在的代码膨胀最小化,使程序的速度提升最大化;

      不要只因为function templates出现在头文件,将它们声明为inline;

条款31:将文件间的编译依存关系降至最低

        在设计对象的过程中,我们可以将对象分割为两个class,一个只提供接口(数据),一个负责实现该接口(函数实现),这就是所谓的pimpl方式;这种设计方式的好处是:1)在修改数据代码时,我们只需要修改接口数据就行,而不要修改实现;2)在函数声明时,当其中有class时,我们只需要声明class而不需要定义class;具体设计策略如下:

              如果使用object reference或者object pointers可以完成任务,就不要使用objects;

              如果能够,尽量以class声明式替换class定义式,这样可以省去调用构造函数的开销;

             为声明式和定义式提供不同的头文件;

   上述的方法称为handle class 的构造方法,常见的handle class的构造方法除了有pimpl方式,还有一种特殊的抽象基类(interface class),这种class的目的是调试derived class 接口,因此通常不带成员变量,没有构造函数,只有一个虚析构函数和一组pure virtual函数,如:

class person{

public:

           virtual ~person( );

           virtual std::string name( ) const=0;

           virtual std::string birthdata( ) const=0;

          virtual std::string address( )const=0;

};

interface class自己创建单个对象,这样的函数称为factory函数或virtual构造函数,它们返回指针(智能指针),指向动态分配所得对象,而该对象支持interface class接口,通常函数被声明为static;代码如下:

class person{

public:

         static std::trl::shared_ptr<person>(create(const std::string&name,const Date&birthday,const address&addr);

};

           Handle class和interface class解除了接口和实现之间的耦合关系,从而降低文件间的编译依存性;对于handle class,成员函数必须通过impl pointer取得对象数据,每一次访问就会增加一层间接性,同时每一个对象消耗的内存数量必须增加impl pointer的大小,最后impl pointer必须初始化,指向一个动态分配的impl object,因此可能会带来bad alloc异常;interface class每次调用只付出一个间接跳跃成本,此外派生的interface class中必须含有一个vptr,这个指针会增加存放对象所需的内存数量。

        在程序开发中,如果使用handle class和interface class的实现码在速度或大小差异大于类之间的耦合时,可以用具体类代替handle class和interface class;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/382386.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis运维和开发学习笔记(6) 监控Redis工作状态-info命令

Redis运维和开发学习笔记(6) 监控Redis工作状态-info命令 文章目录Redis运维和开发学习笔记(6) 监控Redis工作状态-info命令info serverinfo clientinfo memoryinfo persistenceinfo statsinfo commandstatsinfo cpuinfo clusterinfo keyspaceinfo server Redis服务器相关的通用…

Effective C++学习第九天

条款32&#xff1a;确定你的public继承塑模出is-a模型class D&#xff08;derived&#xff09;以public形式继承class B&#xff08;base&#xff09;&#xff0c;则每一个类型为D的对象同时也是一个类型为B的对象&#xff0c;反之不成立&#xff0c;因此B比D表现出更加一般化的…

Effective C++学习第十天

条款36&#xff1a;绝不重新定义继承而来的non-virtual函数non-virtual函数执行的是静态绑定&#xff0c;在编译器就已经决定&#xff0c;因此对象对用的函数只和指针的类型有关&#xff0c;而与指针所指的对象无关&#xff1b;记住non-virtual函数的性质&#xff1a;不变性凌驾…

Redis运维和开发学习笔记(7) 内存管理和过期策略

Redis运维和开发学习笔记(7) 内存管理和过期策略 文章目录Redis运维和开发学习笔记(7) 内存管理和过期策略内存回收策略惰性删除定时任务删除maxmemory过期策略allkeys-lru主从搭建测试搭建完毕主从测试结果volatile-lru测试结果volatile-ttl测试结果allkeys-lru内存回收策略 …

Effective C++学习第十一天

条款41&#xff1a;了解隐式接口和编译期多态面向对象编程世界总是以显式接口&#xff08;源码可见的接口&#xff09;和运行期多态&#xff08;virtual&#xff09;解决问题&#xff1b;对于templates及泛型编程的世界&#xff0c;隐式接口和编译期多态显得更加重要&#xff1…

Redis源码分析(零)学习路径笔记

文章目录第一阶段第二阶段 熟悉Redis的内存编码结构第三阶段 熟悉Redis数据类型的实现第四阶段 熟悉Redis数据库的实现第五阶段 熟悉客户端和服务器端的代码实现第六阶段 这一阶段主要是熟悉Redis多机部分的代码实现关于测试方面的文件有一些工具类的文件如下SORT命令的实现一些…

Effective C++学习第十二天

条款47&#xff1a;请使用traits classes表现类型信息STL有五类迭代器分类&#xff0c;input迭代器&#xff08;只读&#xff0c;一次&#xff0c;向前&#xff09;、output迭代器&#xff08;可写&#xff0c;一次&#xff0c;向前&#xff09;、forward迭代器&#xff08;读/…

Redis源码分析(一)redis.c //redis-server.c

Redis源码分析&#xff08;一&#xff09;redis.c //redis-server.c 入口函数 int main() 4450 int main(int argc, char **argv) {4451 initServerConfig();4452 if (argc 2) {4453 ResetServerSaveParams();4454 loadServerConfig(argv[1]);4455 …

Linux 学习

1.linux文本命令行语言环境设置命令 查看当前语言环境&#xff1a; echo &#xffe5;LANG 修改&#xff1a; LANG选择的语言环境’ 引申&#xff1a;https://blog.csdn.net/huoyunshen88/article/details/41113633 2.linux中的硬链接和软连接 linux中文件的储存方式&#xf…

vivo C/C++工程师视频面试总结 20180802

1.自我介绍&#xff1a;有点儿紧张了&#xff0c;直接把自己简历上的一些信息信息说了一遍&#xff0c;说完之后在介绍了一下自己的平时的爱好和兴趣&#xff0c;感觉面试官没有理我&#xff0c;直接进入下一环节了。 2.项目详情&#xff1a;主要是自己最近的一个项目和自己负…

Redis源码分析(二)redis-cli.c

文章目录1. int main()2. parseOptions(int argc, char **argv) 进行ip和port的改变3. lookupCommand(char *name) 查找命令&#xff0c;判断命令合法3.2 strcasecmp(name,cmdTable[j].name)3.1 redisCommand cmdTable[]4. cliSendCommand(int argc, char **argv)4.1 cliConnec…

C语言中有bool变量吗?

1.C/C中定义的数据类型&#xff1a; C语言中定义了6种基本数据类型&#xff1a;short,int,long,float,double,char 4种构造类型&#xff1a;数组&#xff0c;结构体&#xff08;struct&#xff09;&#xff0c;共用类型(union)&#xff0c;枚举类型(enum) 指针类型和空类型 C语…

redis源码剖析(三)——基础数据结构

文章目录SDS链表字典这篇文章关于 Redis 的基础数据:SDS SDS &#xff08;Simple Dynamic String&#xff09;是 Redis 最基础的数据结构。直译过来就是”简单的动态字符串“。Redis 自己实现了一个动态的字符串&#xff0c;而不是直接使用了 C 语言中的字符串。 sds 的数据结…

C++迭代器使用错误总结

指针和迭代器的区别&#xff1a; 迭代器&#xff1a; &#xff08;1&#xff09;迭代器不是指针&#xff0c;是类模板&#xff0c;表现的像指针。他只是模拟了指针的一些功能&#xff0c;通过重载了指针的一些操作符&#xff0c;->,*, --等封装了指针&#xff0c;是一…

redis源码剖析(四)跳表

文章目录整数集合跳跃表压缩列表总结整数集合 当一个集合只包含整数&#xff0c;且这个集合的元素不多的时候&#xff0c;Redis 就会使用整数集合 intset 。首先看 intset 的数据结构&#xff1a; typedef struct intset {// 编码方式uint32_t encoding;// 集合包含的元素数量…

vivo C/C++工程师 HR视频面试问题总结20180807

一开始没想到这次视频面是HR面试&#xff0c;还以为是技术面试&#xff0c;毕竟上次面试的时候技术问题问的相对比较少&#xff0c;所以面试准备方向有点儿错了&#xff0c;不过还是总结一下具体问题。 1&#xff09;自我介绍&#xff1a;吸取了上次自我介绍的经验&#xff0c;…

在Redis客户端设置连接密码 并演示密码登录

我们先连接到Redis服务 然后 我们要输入 CONFIG SET requirepass “新密码” 例如 CONFIG SET requirepass "A15167"这样 密码就被设置成立 A15167 我们 输入 AUTH 密码 例如 AUTH A15167这里 返回OK说明成功了 然后 我们退出在登录就真的需要 redis-cli -h IP地…

redis源码剖析(五)—— 字符串,列表,哈希,集合,有序集合

文章目录对象REDIS_STRING &#xff08;字符串&#xff09;REDIS_LIST 列表REDIS_SET &#xff08;集合&#xff09;REDIS_ZSET &#xff08;有序集合&#xff09;REDIS_HASH (hash表)int refcount&#xff08;引用计数器&#xff09;unsigned lru:REDIS_LRU_BITS对象 对于 Re…

函数sscanf小结

1.sscanf用于处理固定格式的字符串&#xff0c;包含在头文件<cstdio>中&#xff0c;函数原型为&#xff1a; int sscanf(const char *buffer,const char*format,[]argument ]...); 其中:buffer代表着要存储的数据&#xff0c;format 代表格式控制字符串&#xff0c;arg…

redis源码剖析(六)—— Redis 数据库、键过期的实现

文章目录数据库的实现数据库读写操作键的过期实现数据库的实现 我们先看代码 server.h/redisServer struct redisServer{...//保存 db 的数组redisDb *db;//db 的数量int dbnum;... }再看redisDb的代码&#xff1a; typedef struct redisDb {dict *dict; /*…