【47】请使用traits classes表现类型信息
摘要:1、考虑下面的需求,对迭代器移动d个单位。因为不同类型的迭代器,能力不同,有的迭代器(vector,deque内置迭代器)可以一步到位移动到指定位置,有的迭代器(list内置迭代器)必须一步一步移动,怎么解决?2、很显然,需要判断迭代器的能力,也就是哪一类,不同分类的迭代器采用不同的做法。这需要在运行时判断迭代器的分类,效率低,有没有更好的办法呢?3、很自然地,我们想到增加一个中间层,对不同分类的迭代器封装,对外暴露迭代器的类型信息,也就是在编译器就确定它的分类。具体做法如下: a、每种集合的内置迭代器,都有一个值表示自己的分类,使用typedef 为它的分类取个别名,对外暴露别名; b、..
阅读全文
posted @
2014-01-21 17:54
Andy Niu
阅读(311)
推荐(0) 编辑
【19】设计class犹如设计type
摘要:设计class 的时候,需要好好考虑下面的问题:1、新type的对象应该如何被创建和销毁?2、对象的初始化和对象的赋值该有什么样的差别?3、新type的对象如果pass by value,意味着什么?4、什么是新type的合法值?5、新type需要某个继承体系吗?6、新type需要什么样的转换?7、什么样的操作符和函数对新type的对象是合理的?8、该如何访问新type对象的成员?9、什么是新type未声明的接口?10、新type有多么一般化?是否应该使用template?
阅读全文
posted @
2014-01-20 21:29
Andy Niu
阅读(167)
推荐(0) 编辑
【18】让接口容易被正确使用,不易被误用
摘要:1、所谓软件设计,就是“让软件做出你期望它做的事情”的步骤。首先是构想,考虑对外暴露的接口,然后实现。2、客户没有正确使用自己提供的接口,自己也要负担一部分责任,思考自己的接口是不是简单明了,容易理解。3、对于多个形参的接口,最好表明每个形参的类型和有效范围。4、尽量限制哪些事能做,哪些事不能做,尽早暴露错误。如果没有好的理由,与内置类型保持一致。5、减少用户出错的可能性,考虑,在堆上获取的资源,期望用户使用资源管理类对它封装,但是用户可能忘记。那么,我就对资源封装,并返回给用户一个资源管理对象。对于引用次数为0,执行其它操作的资源管理类,应该在内部指定删除器。
阅读全文
posted @
2014-01-20 21:24
Andy Niu
阅读(316)
推荐(0) 编辑
【12】赋值对象时勿忘其每一个成分
摘要:1、copy构造和copy赋值,我们称为copy方法。当我们声明自己的copy方法时,就意味着,告诉编译器,我自己定义copy构造和copy赋值,不要你自动生成。这种情况下,自己要保证复制对象的时候,不要忘记每一个成分。2、开始定义一个类的时候,在copy构造我们会初始化每一个字段,在copy赋值我们会为每个字段赋值,不会出错。后来,当我们在类中增加一个字段,很可能会忘记初始化这个字段,或者为它赋值。这种情况,要特别注意。3、当出现继承的时候,存在潜在的问题。子类会继承父类的字段,即使是private字段也继承下来,只不过不能访问。这种情况下,不要忘记父类中的字段。4、copy构造的时候,在初
阅读全文
posted @
2014-01-20 21:02
Andy Niu
阅读(267)
推荐(0) 编辑
【11】在operator=中处理“自我赋值”
摘要:1、自我赋值,看起来愚蠢,但是却合法。有些自我赋值一眼就可看出来。有些自我赋值是潜在的。比如:a[i] = a[j]; *px = *py; 甚至不同类型的指针,都指向同一个地址,也是自我赋值,这一类自我赋值,很难识别,因此对自我赋值要有一定的防范。2、对于资源管理类auto_ptr和shared_ptr,自我赋值是安全的。如果自行管理资源,比如Widget中有个Bitmap* pb;copy赋值如下:1 Widget& Widget::operator=(const Widget& rhs)2 {3 delete pb;4 pb = new Bitmap(*(rhs.pb))
阅读全文
posted @
2014-01-20 20:35
Andy Niu
阅读(1229)
推荐(0) 编辑
【25】考虑写出一个不抛异常的swap函数
摘要:1、swap交换对象值,std提供了实现方法模版的实现,它的实现是交换对象值。 1 namespace std 2 { 3 template 4 void swap(T& a,T& b) 5 { 6 T temp(a); 7 a = b; 8 b = a; 9 }10 }2、考虑下面的情况下,资源管理类Widget,内部只有一个指针WidgetImpl,该指针指向一个对象,这个对象中包含真正数据。对这种资源管理类执行swap,如果调用std中的swap,有哪些动作?对于Widget,copy构造一次,copy赋值两次,同时对于WidgetImpl,copy构造一次,cop...
阅读全文
posted @
2014-01-20 19:56
Andy Niu
阅读(615)
推荐(0) 编辑
【09】绝不在构造和析构过程中调用virtual方法
摘要:1、绝不在构造和析构过程中调用virtual方法,为啥? 原因很简单,对于前者,这种情况下,子类专有成分还没有构造,对于后者,子类专有成分已经销毁,因此调用的并不是子类重写的方法,这不是程序员所期望的。2、在构造方法和析构方法中,直接调用virtual方法,很容易识别。还有一种情况要注意,那就是间接调用。比如:构造方法调用init方法,而init方法调用virtual方法。3、在构造过程中,不能使用virtual从上到下调用,哪有什么办法弥补呢? 可以将子类必要的信息向上传递给父类构造方法。
阅读全文
posted @
2014-01-16 21:11
Andy Niu
阅读(266)
推荐(0) 编辑
【08】别让异常逃离析构函数
摘要:1、考虑一个类Widget,析构方法抛出异常,一个集合中包含10个Widget对象,集合离开作用域,逆序撤销集合里的对象,最后一个抛出异常,倒数第二个抛出异常,问题出现了,这种情况下,要么导致程序结束,要么导致不明确行为。2、因此,绝不能在析构方法中抛出异常。考虑,数据库连接类DBConn,用户可能会忘记关闭连接,因此在DBConn的析构方法中,执行Close方法。那么问题来了,close方法可能会抛出异常,怎么办? a、在析构方法中,捕获异常,结束程序,这种方法可用性太差。 b、在析构方法中,捕获异常,吞下异常,这种方法忽略错误,程序继续运行,接下来可能会导致不明确行为。3、上面两种解决..
阅读全文
posted @
2014-01-16 21:00
Andy Niu
阅读(286)
推荐(0) 编辑
【07】为多态基类声明virtual析构方法
摘要:1、考虑下面的需要,需要一个工厂方法。工厂方法的规则是:在堆上分配一个子类对象,并返回父类指针。使用完毕,delete父类指针。如果父类的析构方法不是virtual,将直接调用父类的析构方法,导致局部销毁的对象,父类成分销毁了,子类成分没有销毁。2、如果类中有virtual方法,意味着面向抽象编程,也就是会有父类指针指向子类对象,因此这种情况下,必定需要一个virtual析构方法。3、如果类中没有virtual方法,为了节省空间,析构方法是non-virtual方法。也就意味着,这个类不作为父类。如果你继承这样的类,就会导致上面的局部销毁对象。Java中有final,C#中sealed,禁止继
阅读全文
posted @
2014-01-16 19:53
Andy Niu
阅读(362)
推荐(0) 编辑
【06】若不想使用编译器自动生成的函数,就该明确拒绝
摘要:1、有些情况下,我们不希望对象被拷贝,比如独一无二的对象,拷贝是没有意义的。这种情况下,我们应该禁止拷贝。也就是不提供copy构造和copy赋值,可问题是,如果我们不提供,编译器会自动生成。那该怎么办?2、我们声明copy构造和copy赋值为private,这就明确告诉了编译器,我已经声明了,你别为我生成了。由于copy构造和copy赋值是private,就禁止了外部的拷贝操作。 但仔细想,还是有问题,因为类的成员方法,友元方法,友元类还是可以访问private权限的copy构造和copy赋值,那该怎么办? 只声明,不定义。这样,就导致编译器连接的时候出错。有没有更好的办法呢?也就是说,将..
阅读全文
posted @
2014-01-15 20:19
Andy Niu
阅读(467)
推荐(0) 编辑
【05】了解C++默默编写并调用那些函数
摘要:1、如果没有声明copy构造方法,copy赋值操作符,和析构方法,编译器会自动生成这些方法,且是inline。2、如果没有声明任何构造方法,编译器会自动生成一个default构造方法,且是inline。3、编译器总是为我们生成一个合成析构方法。4、首先考虑,编译器生成的copy构造方法做什么事? copy构造方法是一个特殊的构造方法,形参是const T&。编译器生成的copy构造方法逐个成员初始化。逐个成员初始化:对于创建对象的每个non-static字段,使用现有对象的字段去初始化。 如果字段是类类型,递归调用类的copy构造方法。如果是内置类型,逐个bit拷贝。需要注意的是:对于
阅读全文
posted @
2014-01-15 19:49
Andy Niu
阅读(372)
推荐(0) 编辑
【04】确定对象被使用之前已先被初始化
摘要:1、首先区分初始化和赋值。 初始化:创建对象时赋给初始值。赋值:擦除对象当前值,并用新值代替。因此,区分初始化和赋值的关键是,看看对象当前是否已经有值了。2、创建对象的时候,如果没有初始化,对象的值是什么? 取决于对象的类型和对象定义的位置。 a、如果对象是内置类型,定义在方法外,它的值为0,定义在方法内,没有初始化。 b、如果对象是类类型,无论定义在哪里,都会调用默认构造方法,也就是说,对于类类型的对象,不存在没有初始化的情况。3、现在考虑,类中的数据成员,也就是字段。类的构造方法执行两个过程:成员初始化列表和方法内赋值。对于类类型,前者用于初始化,即copy构造;后者用于赋值,即c...
阅读全文
posted @
2014-01-14 19:28
Andy Niu
阅读(576)
推荐(0) 编辑
【03】尽可能使用const
摘要:1、为什么搞出const关键字? const指定一个语义约束,指定一个对象不可修改。如果一个对象不可修改,就应该说出来。2、const与指针 const可以修饰指向之物,也可以修改指针本身。STL中的迭代器是对指针的封装,因此,迭代器也有两个概念:指向常量对象的迭代器和常量迭代器。 vector::const_iterator; //指向常量的迭代器 const vector::iterator; // 常量迭代器3、const可以与方法产生关联,可以用在方法前,方法中(形参表中),方法后。首先考虑,用在方法前,表示返回值是常量。为什么要返回const对象呢? 我们知道,方法返回值是...
阅读全文
posted @
2014-01-13 20:43
Andy Niu
阅读(351)
推荐(0) 编辑
【28】避免返回handles指向对象内部成分
摘要:1、为什么? 很简单,你指向箱子里面的一个物品,使用这个物品。但是箱子不受你控制,箱子销毁了,里面的物品也会随之销毁。那么这种情况下,你指向的就是一堆垃圾,你还在使用这个物品,导致未定义的行为。
阅读全文
posted @
2014-01-10 19:19
Andy Niu
阅读(151)
推荐(0) 编辑
【27】尽量少做转型动作
摘要:1、C++是强类型语言,保证类型错误不会发生,转型会破坏类型系统。C语言提供了强制转型语法T(expn),或者T(expn),C++提供了新式转型。 const_cast(expn) static_cast(expn) dynamic_cast(expn) reinterpret_cast(expn) 强烈建议使用新式转型,因为:一是在代码中容易识别出来,二是各转型动作的目标窄化,编译器容易诊断出错误,可以简单认为,细化分工比原来的万能方式好。2、尽量避免转型,为什么?转型往往会影响效率,转型后出现临时对象,而应该使用无需转型的替代方法,比如virtual。
阅读全文
posted @
2014-01-10 19:13
Andy Niu
阅读(196)
推荐(0) 编辑
【43】学习处理模版化基类内的名称
摘要:1、考虑下面的场景,有个类模版template Box{}; 内部有个方法XXX,现在子类template class BigBox:public Box{},BigBox内部使用方法XXX,考虑出现什么问题?2、首先一点,类模版产生出来的类之间,没有关系。也就是Box 与Box没有关系。通过指定模版实参,可以产生不同的模板类,也可以全特化一个类,如下: template class Box{...},这种情况下,就不再实例化模版类了,Box可以声明自己的接口,可能就没有了XXX。那么,问题出现了,BigBox继承Box,但是父类Box可能有接口XXX,也可能没有接口XXX,编译器傻眼了...
阅读全文
posted @
2014-01-10 18:37
Andy Niu
阅读(336)
推荐(0) 编辑
【42】了解typename的双重意义
摘要:1、在template声明中,class与typename是等价的,但是使用typename更好。2、在template实现中,模版形参是从属名称,嵌套在模版形参中的类型是嵌套从属名称,不依赖任何template参数的名称,称为非从属名称。3、嵌套从属名称,可能会导致解析困难,出现二义性。比如:C::const_iterator* x; 有两种意思。一是:模版形参C中有个静态字段const_iterator,然后计算它与x的乘积;二是:模版形参C中有个嵌套类型const_iterator,定义指向它的指针。默认情况下,C++编译器按第一种意思解释,也就是说,把它当成静态字段,而不是类型。如果我
阅读全文
posted @
2014-01-10 18:02
Andy Niu
阅读(545)
推荐(0) 编辑
【41】了解隐式接口和编译期多态
摘要:1、当初为什么设计C++ Template? C++ Template是为了建立类型安全的容器,如vector,可以建立包含Person的vector,也可以建立包含int的vector。后来发现,C++ Template具备更强大的威力,泛型编程,也就是写出的代码与所处理的对象类型彼此独立。2、面向对象总是显式接口和运行期多态,这个很好理解。显式接口就是类的头文件中暴露的方法和字段,运行时多态就是运行时根据对象的真实类型决定调用哪个方法。在泛型编程中,暴露的接口是隐式接口,也就是一组有效表达式,期望类的对象具有这些接口,如果没有代码编译通不过。编译时多态是指:在编译时根据模版实参实例化出一.
阅读全文
posted @
2014-01-10 17:41
Andy Niu
阅读(299)
推荐(0) 编辑
【17】以独立语句将newed对象置入智能指针
摘要:1、为什么? 考虑下面的情况:方法声明为void processWidget(shared_ptr pw,int priority)。 调用方法 processWidget(shared_ptr (new Widget), getPriority()),在调用方法之前,要做三件事: a、执行new Widget b、调用shared_ptr的构造方法 c、调用方法getPriority() 可问题是:C++不保证上面的执行顺序(C#,Java保证)。那么问题就来了,如果是acb的执行顺序,并且c步骤异常,那么new出来的资源还没有放进资源管理类,导致内存泄漏。2、如何解决上面的问题...
阅读全文
posted @
2014-01-09 20:44
Andy Niu
阅读(148)
推荐(0) 编辑
【16】成对使用new和delete时要采取相同形式
摘要:简而言之,new时不带[],delete时也不带[];new时带[],delete时也要带[]。如果不匹配,要么造成多销毁对象,导致未定义行为;要么导致少销毁对象,导致内存泄漏。
阅读全文
posted @
2014-01-09 20:03
Andy Niu
阅读(130)
推荐(0) 编辑