C++学习(8)--转向effective c++(完)

来源:互联网 发布:金蝶引出数据失败 编辑:程序博客网 时间:2024/05/22 13:39
条款42: 明智地使用私有继承
如果两个类之间的继承关系为私有,编译器一般不会将派生类对象(如Student)转换成基类对象(如Person)。
从私有基类继承而来的成员都成为了派生类的私有成员,即使它们在基类中是保护或公有成员。
私有继承意味着 "用...来实现"。
如果使类D私有继承于类B,这样做是因为你想利用类B中已经存在的某些代码,而不是因为类型B的对象和类型D的对象之间有什么概念上的关系。
因而,私有继承纯粹是一种实现技术。
是不是很熟悉?哈哈,和四十条中的分层没什么出入,其实际确实也没有什么出入。
如何区分使用?
尽可能地使用分层,必须时才使用私有继承。
如果我们使用模版类的话,不同的类型遍会生成各自类型的对象
例如Stack模板,构成Stack<int>成员函数的代码和构成Stack<double>成员函数的代码是完全分开的。
有时这是不可避免的,但即使模板函数实际上可以共享代码,这种代码重复还是可能存在。
这种目标代码体积的增加有一个名字:模板导致的 "代码膨胀"。这不是件好事。
对于某些类,可以采用通用指针来避免它。采用这种方法的类存储的是指针,而不是对象,实现起来就是:
创建一个类,它存储的是对象的void*指针。
创建另外一组类,其唯一目的是用来保证类型安全。这些类都借助第一步中的通用类来完成实际工作。
用分层的方式(层外是另一组类,层内是创建的类)固然可以很好的解决这样的问题。
可是他们却可以绕过接口类直接访问原来储存通用指针的那个类。
这个时候就可以用私有继承了
和分层的方法一样,基于私有继承的实现避免了代码重复,因为这个类型安全的接口类只包含有对函数的内联调用。
而此时,你又可以用一个模版类来写这个私有继承的类!!!最终又回到了模版,而中间的工作却含辛茹苦,效果惊人
来看看这个fantastic的类吧!
template<class T>
class Stack: private GenericStack {
public:
  void push(T *objectPtr) { GenericStack::push(objectPtr); }
  T * pop() { return static_cast<T*>(GenericStack::pop()); }
  bool empty() const { return GenericStack::empty(); }
};
实现了原类,使用了通用指针,使用了私有继承,就是把一个无效率的模版更改成了有效率的模版
其中的精神值得学习!
条款43: 明智地使用多继承
一个充满争议的话题,暂时先不看,等使用到多继承的时候再说……
 

条款44: 说你想说的;理解你所说的
这个条款估计是为了凑足五十条而加上去的,大部分是前面条款的重复。我就直接粘贴上来吧。
公有继承和 "是一个" 的等价性,以及非虚成员函数和 "特殊性上的不变性" 的等价性,是C++构件如何和设计思想相对应的例子。
下面的列表总结了这些对应关系中最重要的几个。
共同的基类意味着共同的特性。如果类D1和类D2都把类B声明为基类,D1和D2将从B继承共同的数据成员和/或共同的成员函数。见条款43。
公有继承意味着 "是一个"。如果类D公有继承于类B,类型D的每一个对象也是一个类型B的对象,但反过来不成立。见条款35。
私有继承意味着 "用...来实现"。如果类D私有继承于类B,类型D的对象只不过是用类型B的对象来实现而已;类型B和类型D的对象之间不存在概念上的关系。见条款42。
分层意味着 "有一个" 或 "用...来实现"。如果类A包含一个类型B的数据成员,类型A的对象要么具有一个类型为B的部件,要么在实现中使用了类型B的对象。见条款40。
下面的对应关系只适用于公有继承的情况:见条款36。
纯虚函数意味着仅仅继承函数的接口。如果类C声明了一个纯虚函数mf,C的子类必须继承mf的接口,C的具体子类必须为之提供它们自己的实现。
简单虚函数意味着继承函数的接口加上一个缺省实现。如果类C声明了一个简单(非纯)虚函数mf,C的子类必须继承mf的接口;如果需要的话,还可以继承一个缺省实现。
非虚函数意味着继承函数的接口加上一个强制实现。如果类C声明了一个非虚函数mf,C的子类必须同时继承mf的接口和实现。实际上,mf定义了C的 "特殊性上的不变性"。

条款45: 弄清C++在幕后为你所写、所调用的函数
这一段颇有点primer的风范了了,写得具体,全面,密不透风,向精简概括都不行,我要一五一十的把他们粘贴下来!
//////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////
如果你没有声明下列函数,体贴的编译器会声明它自己的版本。这些函数是:一个拷贝构造函数,一个赋值运算符,一个析构函数,一对取址运算符。另外,如果你没有声明任何
构造函数,它也将为你声明一个缺省构造函数。所有这些函数都是公有的。换句话说,如果你这么写:
class Empty{};
和你这么写是一样的:
class Empty {
public:
  Empty();                        // 缺省构造函数
  Empty(const Empty& rhs);        // 拷贝构造函数
  ~Empty();                       // 析构函数 ---- 是否
                                  // 为虚函数看下文说明
  Empty&
  operator=(const Empty& rhs);    // 赋值运算符
  Empty* operator&();             // 取址运算符
  const Empty* operator&() const;
};
现在,如果需要,这些函数就会被生成,但你会很容易就需要它们。下面的代码将使得每个函数被生成:
const Empty e1;                     // 缺省构造函数
                                    // 析构函数
Empty e2(e1);                       // 拷贝构造函数
e2 = e1;                            //  赋值运算符
Empty *pe2 = &e2;                   // 取址运算符
                                    // (非const)
const Empty *pe1 = &e1;             //  取址运算符
                                    // (const)
////////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////////
这里主要讨论缺省拷贝构造函数(赋值运算符)
缺省拷贝构造函数(赋值运算符)对类的非静态数据成员进行 "以成员为单位的" 逐一拷贝构造(赋值)。
-----"以成员为单位的" 逐一拷贝构造(赋值)-----
什么意思?
如果m是类C中类型为T的非静态数据成员,并且C没有声明拷贝构造函数(赋值运算符)
m将会通过类型T的拷贝构造函数(赋值运算符)被拷贝构造(赋值)---- 如果T有拷贝构造函数(赋值运算符)的话。
如果没有,规则递归应用到m的数据成员,直至找到一个拷贝构造函数(赋值运算符)或固定类型(例如,int,double,指针,等)为止。
默认情况下,固定类型的对象拷贝构造(赋值)时是从源对象到目标对象的 "逐位" 拷贝。
对于从别的类继承而来的类来说,这条规则适用于继承层次结构中的每一层
所以,用户自定义的构造函数和赋值运算符无论在哪一层被声明,都会被调用。
就是这个意思,那么什么叫做 "逐位" 拷贝呢?我想应该就是按固定类型的位数(int-2)拷贝的吧~
如果想让一个包含引用成员的类支持赋值,就得自己定义赋值运算符。
因为对引用的拷贝无疑是将拷贝的数据同样指向被拷贝的数据,达不到拷贝的涵义,更是无法编译的。

条款46: 宁可编译和链接时出错,也不要运行时出错
比较宽泛的题目,所讲的也就是概括成为题目上的那两句话:
将检查从运行时转移到编译或链接时一直是值得努力的目标,只要实际可行,就要追求这一目标。这样做的奖赏是,程序会更小,更快,更可靠。

条款47: 确保非局部静态对象在使用前被初始化
非局部静态对象指的是这样的对象:
定义在全局或名字空间范围内(例如:theFileSystem和tempDir),
在一个类中被声明为static或在一个文件范围被定义为static。
条款48: 重视编译器警告
忽视编译器警告几乎肯定会导致错误的程序行为。
你会不停地调试去找原因,而这个错误实际上早就被编译器发现了。
在忽略一个警告之前,你一定要准确理解它想告诉你的含义。
在编程时不要马马虎虎,寄希望于编译器为你找出每一条错误。
条款49: 熟悉标准库
我想看完c++之后有两个要看的冬冬
一个是VC,去探索它的MFC
一个是STL,去研究它的各种各样的函数库
可能这遍是下个学期大部分的使命了
抑或是寒假的时候就能把他们搞定……
加油了……
条款50: 提高对C++的认识
想要继续提高对C++的认识,就是要读更多的书,理解C++“为什么会这样去做”
诚然,最后几条已经成了空洞的勉励和提示,真正的技术上的东西并不多。
但是这些确实为今后走向更深层次的C++所描写的最友情的资源。
Effective C++将告一段落,当然,学习一本书的过程并不止于几天之内将他读完
而在于记住书中最有意义的内容,熟悉应该知道的内容,当在日后的使用过程中可以查找到这些内容
当然,懒惰的我并没有真正将这本书全部看完,除去觉得暂时用不上的原因
其内容晦涩难懂可能也是个借口,其中包括:
8,9,10,16,28,33,34,43,47这九个条款

条款8: 写operator new和operator delete时要遵循常规
条款9: 避免隐藏标准形式的new
条款10: 如果写了operator new就要同时写operator delete
条款16: 在operator=中对所有数据成员赋值
条款28: 划分全局名字空间
条款33: 明智地使用内联
条款34: 将文件间的编译依赖性降至最低
条款43: 明智地使用多继承
条款47: 确保非局部静态对象在使用前被初始化
 
原创粉丝点击