Effective C++学习笔记

Posted Karthus_冲冲冲

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Effective C++学习笔记相关的知识,希望对你有一定的参考价值。

目录

条款18. 让接口更容易被正确使用

    1. 好的接口容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
      避免接口误用的措施:
      (1)引入新类型可以预防接口被误用。如果客户误用,则编译器被提醒类型不匹配:


      (2)以函数代替对象。例如月份只有12个月,用枚举变量不安全,因此可以用函数代替对象:
      (3)const修饰:限制类型什么能做,什么不能做。

      (4)消除客户的资源管理责任:为了防止客户忘记使用智能指针而造成内存泄漏,接口应该提供在创建资源后,强制返回一个指向该资源的智能指针
      ,先发制人。
    1. 尽量让你的types的行为与内置types的行为一致,相兼容。
    1. 智能指针shared_ptr支持自定制删除器,可以有效防范动态链接库DLL问题(对象在一个DLL中被new,却在另一个DLL内被delete)

条款19. 设计class犹如设计type

    1. class设计就是type设计。定义一个新的type之前,请考虑:新type的创建与销毁、初始化、赋值操作、值传递、合法值、继承体系(virtual)、类型转换、需要重载的操作符等等。

条款20. 宁以pass-by-reference-to-const替换pass-by-value

    1. 尽量以传const引用代替传值,传引用更加高效,并且可避免切割问题
      (1)by value需要创建新对象,可能需要调用多次构造和析构,而by reference没有任何新对象被创建。
      (2)切割问题:当一个派生类对象以by value方式传递并被视为一个基类对象,那么基类的copy构造函数将会被调用,而原对象派生类的新性质被完全切割掉了,仅剩一个基类对象。这也会影响多态性质,错误调用重载的虚函数。而传引用不会发生以上情况。
    1. 以上规则并不适用于内置类型以及STL的迭代器和函数。它们更适合值传递。 因为本质上,const引用底层传递的是指针。

条款21. 必须返回对象时,别妄想返回其reference

    1. 绝不要返回指向一个局部栈对象的指针或引用:因为函数栈变量会在函数执行完毕后自动销毁,这时指向局部栈对象的指针或引用将会指向被销毁的内存,行为无定义。
    1. 绝不要返回指向一个堆分配的对象的引用:例如对返回引用的堆对象进行连续运算,连续运算会产生的中间无名对象。我们无法找到其指针为其进行析构,容易发生内存泄漏。
    1. 绝不要返回指向一个局部静态对象的指针或引用:它们可能同时需要多个这样的对象,但事实只有一个静态对象(如下例ab的结果与cd的结果均是返回的同一个局部静态对象,if条件判断始终为真):

    1. 上述例子必须返回新对象的函数正确写法

条款22. 将成员变量声明为private

    1. 切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供class作者以充分的实现弹性
      (1)将成员变量设为private,使用函数可以实现对成员变量的处理精确控制。如果成员变量设为public,客户可以轻松读写成员变量,不安全且破坏封装性。此外,如果某一天你提供给客户的代码需要更新,而客户代码中有大量部分直接用到了成员变量,客户得到你的新代码库后需要修改大量的地方,并且重新编译,十分麻烦。如果用函数访问代替,客户将只用调用函数接口,不直接访问变量。只要函数接口不变,对象内部的设计变化弹性将会更大,有利于后续扩展新的功能,并且客户将不用为此修改代码、大量重新编译等等。
    1. protected并不比public更具有封装性:假如我们有一个protected变量,而最终取消了它,那么使用它的派生类的代码都会被破坏。从封装的角度来说,只有两种访问权限:private(提供封装)和其他(不提供封装)

条款23. 宁以non-member、non-friend替换member函数

    1. 宁以non-member、non-friend替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性:
      (1)越多东西被封装,我们在接口背后改变这些东西的能力就越大。
      (2)导致较大封装性的是non-member non-friend函数,因为它并不增加“能够访问class内private成分”的函数数量。
      (3)C++中,比较自然的做法是将一个非成员函数和类定义放在同一个namespace(命名空间)里。namespace可以跨越多个源码文件,可以轻松扩展在不同的文件中定义用于该class的非成员便利函数,只需用相同的命名空间就行。

条款24. 若所有参数皆需类型转换,请为此采用non-member函数

    1. 令class支持隐式类型转换通常是个糟糕的主意,当然也有例外。最常见的例外就是建立数值类型(需要用到隐式类型转换)。
    1. 若成员函数参数需要隐式类型转换,混合运算可能会报错:只有当参数被列于参数列内,这个参数才是可以隐式转换的。下例中,oneHalf2可以是因为编译器自动识别出了传递的整型2(位于参数列内所以才识别出),但函数需要一个Rational对象,因此自动调用Rational的构造函数(必须是non-explicit,允许隐式转换)用整型2构造一个Rational对象。而2oneHalf报错是因为这个整型2并不位于Rational的操作符*函数参数列内,无法进行隐式转换。


    1. 当operator * 成为一个非成员函数(能避免friend就避免friend)时,便允许编译器在每个参数上执行隐式转换

条款25. 考虑写出一个不抛出异常的swap函数

    1. 标准库中缺省版的swap函数无非就是a复制到tmp,b复制到a,tmp复制到b。然而当对象体积过于庞大时,反复复制会影响执行效率。可以设计一个这种体积庞大类的管理类,然后在管理类中用一个成员变量指针指向这个庞大类,swap就可以只交换指针即可。该方法被称为“pimpl手法”(pointer to implementation),这时通常需要自定义swap函数,同时要避免其抛出异常,因为swap的一个最好应用就是帮助class提供强烈的异常安全性保障。高效的swap函数几乎总是基于内置类型的操作(例如指针),而内置类型上的操作绝不会抛出异常
    1. 当缺省swap效率不足时,请尝试做以下事情
      (1)提供一个public swap成员函数,让它高效地置换你的类型的两个对象值;
      (2)在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述swap成员函数;

      (3)如果你正编写一个class (而非class template),为你的class特化std::swap。并令它调用你的swap成员函数。

      最后,如果你调用swap,请确定包含一个using声明式,以便让 std: :swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap

    1. @注意@:为用户自定义类型进行全特化std的templates,但是不可以添加新的templates到std里

《Effective C++ 》学习笔记——条款11

***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************



二、Constructors,Destructors and Assignment Operators


Rule 11:Handle assignment to self in operator =

规则11:在 operator= 中处理“自我赋值”



1.自我赋值?!

比方这种:

class Widget  {  ...  };
Widget w;
...
w = w;    // 赋值给自己

这样做是同意的,所以不要期盼不会发生,由于鸟大了,什么林子都有 o(╯□╰)o。。


2.自我赋值 其它形式

① 除了最直观的   w=w

② 另一些隐蔽的,比方:

a[i] = a[j]; // 万一 i等于j

③ *px = *py 

也许。这两个指针都指向同一个对象

④ 还有 base class 与 derived class的:(由于一个基类的引用或者指针。能够指向派生类对象)

class Base  {  ...  };
class Derived : public Base  {  ...  };
void doSomething( const Base& rb , Derived* pd);<span style="white-space:pre">	</span>// rb 和 pd 可能指向同一个对象


总结下来就是,假设某段代码操作pointers 或 references,而它们被用来“指向多个同样类型的对象”,就须要考虑这些对象是否为同一个,并且假设对象来自同一个继承体系。它们甚至不须要声明为同类型就可能造成“别名”(如上面的④)。



3.愚蠢的自我赋值,会导致?

class Bitmap  {  ...  };
class Widget  {
    ...
private:
    Bitmap* pb;
};

Widget& Widget::operator=( const Widget& rhs )<span style="white-space:pre">		</span>// 一分不安全的 operator= 实现版本号
{
    delete pb;<span style="white-space:pre">						</span>// 停止使用当前的 Bitmap
    pb = new Bitmap(*rhs.pb);<span style="white-space:pre">				</span>// 使用 rhs's的Bitmap 副本
    return *this;<span style="white-space:pre">					</span>// 这个问题在上一个条款(条款10)中介绍过
}

这个样例说明了虾米呢?

就是,假设是

a = b;

工作原理是。先把a的版本号删除。然后在将b赋值给a。

所以,假设a与b 是同一个东西,它就会导致错误,

按上面的样例来讲,就是,删除pb的同一时候。rhs也被删除了,所以第二行的赋值动作就会指向一个已经被删除的对象。



4.看到错误了,总归要解决的

有三种解决方式

<1> 第一种方案——“证同測试” identity test

Widget& Widget::operator=(const Widget& rhs )
{
    if( this == &rhs )    return *this;<span style="white-space:pre">		</span>// identity test
    
    delete pb;
    pb = new Bitmap(*rhs.pb);
    return *this;
}
就是在运行代码前。先推断是否是同一个东西,若是,就不须要做不论什么事情。直接返回。


<2> 另外一种方案——先赋值,再删除

Widget& Widget::operator=(const Widget& rhs)
{
    Bitmap* pOrig = pb;<span style="white-space:pre">			</span>// 先备份
    pb = new Bitmap(*rhs.pb);<span style="white-space:pre">		</span>// 赋值
    delete pOrig;<span style="white-space:pre">			</span>// 删除备份
    return *this;
}
就是仅仅须要注意在复制之前别删除原来的,先备份一下。

这也许不是处理“自我赋值”最高效的办法。但它行得通。


<3> 第三种方案——copy and swap

class Widget  {
    ...
    void swap( Widget& rhs );<span style="white-space:pre">				</span>// 交换*this和rhs的数据,在后面条款29会有具体解释
    ...
};
Widget& Widget::operator=( const Widget& rhs )
{
    Widget temp(rhs);<span style="white-space:pre">					</span>// 为rhs数据制作一份复件
    swap(temp);
    return *this;
}

并且,还有还有一种形式。对于:①某class的 copy assignment操作符声明为“以 by value 方式接受实參”。②以by value方式 传递东西会造成一份复件:

Widget& Widget::operator= ( Widget rhs )<span style="white-space:pre">	</span>// rhs是被传递对象的一份复件
{<span style="white-space:pre">		</span>
    swap(rhs);<span style="white-space:pre">					</span>// 注意这里是 pass by value
    return *this;<span style="white-space:pre">				</span>// 将*this的数据和复件的数据互换
}

而这样的做法也是作者比較不推荐的。由于它为了 伶俐巧妙的修补 牺牲了 代码的清晰性。可读性略低。

但 将 copying 动作 从函数本体 移至 函数參数构造阶段 却能够 让编译器产生更高效的代码。



5.请记住

★ 确保当对象自我赋值时 operator= 有良好行为。当中技术包含比較“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap。

★ 确定不论什么函数假设操作一个以上的对象,而当中多个对象是同一个对象时,其行为仍然正确。



***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************


以上是关于Effective C++学习笔记的主要内容,如果未能解决你的问题,请参考以下文章

《Effective C++ 》学习笔记——条款11

《Effective C++》学习笔记

Effective C++学习笔记

Effective C++学习笔记

《Effective C++》读书笔记汇总

学习日记之模板方法模式和 Effective C++