Effective C++读书笔记(条款18-23)

(四).设计与声明

____________________________________________________________________________________________________________________________________

条款18:让接口容易被使用,而不容易被误用

#1.导入新类型可以让接口不易被误用,以函数替换对象则可以保证类型安全性。

例如:

class Date{
 public:
    Date(int month, int day, int year);
    ...
}
//加上外覆类型:
class Month{
public:
    static Month Jan(){return Month(1);}
    static Month Feb(){return Month(2);}
    ...
    static Month Dec(){return Month(12);}
    ...
private:
    explicit Month(int m);
    ...
};
struct Day{
explicit Day(int d)
    : val(d) {}
int val;
};
struct Year{
explicit Year(int y)
    :val(y){}
int val;
};
//则可以这样调用:
Date d(Month::Mar(), Day(30), Year(1995));
//从而保证了类型安全性和接口不易被误用。

#2.限制类型内什么事可做,什么事不可做可以预防客户错误。

例如,用const修饰operator*的“返回类型”可阻止客户因”用户自定义类型“而犯错。

if(a*b = c)...  //喔噢,原意其实是要做一次比较动作

#3."保证接口的一致性","让types行为与内置types的行为一致”可以促进接口被正确使用。

#4.任何接口要求客户必须记得做某些事情,就是有着”不正确使用“的倾向。

例如,有函数:

Investment* createInvestment();

并希望最后将Investment*指针传递给名为 getRidOfInvestment()函数,则给了

客户三次犯错误的机会:没有删除指针,删除指针多次,没有用getRidOfInvestment()函数来删除指针

我们可以将其改成如下代码:

std::tr1::shared_ptr<Investment> createInvestment()
{
    std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0),
                    getRidOfInvestment);
    retVal = ...;   //另retVal指向正确对象
    return retVal;
}

这样便可以防止客户犯错,避免造成资源泄漏。

#5.tr1::shared_ptr支持定制型删除器(custom deleter),这可用来防范DLL问题,可被用来

自动解除互斥锁(mutexes;见条款14)等。

____________________________________________________________________________________________________________________________________

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

#1.新type的对象应该如何被创建和消耗?

#2.对象的初始化和对象的赋值该有什么样的差别?

#3.新type如果被passed by value(以值传递),意味着什么?

#4.什么是新type的"合法值"?

#5.你的新type需要配合某个继承图系(inheritance graph)吗?

#6.你的新type需要什么样的转换?

#7.什么样的操作符和函数对此新type而言是合理的?

#8.什么样的标准函数应该被驳回?

#9.谁该取用新的type的成员?

#10.什么是type的"未声明的接口"(undeclared interface)?

#11.你的新type有多么一般化?

____________________________________________________________________________________________________________________________________

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

#1.对于自定义类型,尽量以pass-by-reference-to-const 替换 pass-by-value,前

者通常比较高效,而且可以避免切割问题。

(避免构造函数和析构函数所带来的成本)

#2.对于内置类型,STL的迭代器及函数对象,pass-by-value比较适当。

(这部分符合C守则,没有构造函数带来的成本, pass-by-value通常比指针来实现

的reference更高效)

#3.对于小型自定义类型,仍应使用passed-by-reference-to-const。

理由1:

对于小型拥有并不昂贵的copy构造函数,某些编译器对待”内置类型“和”用户自定义类型“

的态度截然不同,纵使两者拥有相同的底层表述。

例如:

某些编译器拒绝把只由一个double组成的对象放进缓存器内,却很乐意在一个正规基础上对

光秃秃的double这么做。

因此使用passed-by-reference-to-const传递比passed-by-value更好。

理由2:作为一个用户自定义类型,其大小容易有所变化。

理由3:改用不同的C++编译器可能改变type的大小,比如:某些标准程序库实现版本中的

string类型比其他版本大七倍。

____________________________________________________________________________________________________________________________________

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

#1.用pointer或reference指向一个local stack对象,并在函数中返回,都将导致

”无定义境地“的不明确行为,因为locak stack对象在函数返回时就被销毁了。

#2.返回pointer或reference指向一个local static而有可能需要多个这样的对象

会使多个对象同时指向一个对象,这很可能并不是一开始你所希望的。

例如:

const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    static Rational result;
    result = ...;
    return result;
}
bool operator==(const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
...
if((a*b)==(b*d)){
 //当乘积相等时,做适当的相应动作;
}else{
 //当乘积不相等时,做适当的相应动作;
}

结果总是执行相等时的语句块,因为返回的总是同一个local static对象,

这并不是当初我们所希望的。

____________________________________________________________________________________________________________________________________

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

#1.切记将成员变量声明为private,这可赋予客户

(1).访问数据的一致性(都通过函数访问),

(2).可细微划分的访问权限(不准访问,只读访问,只写访问,读写访问),

(3).允许约束条件获得保证(通过函数验证class的约束条件,通知其他对象,

验证函数的前提和事后状态,在多线程状态中执行同步控制...等等。)

(4).增加class的封装性,及实现弹性(访问控制增加封装性;客户统一调用,

却可改变的函数内部行为增加了实现弹性)

(5).确保class的约束条件总是会获得维护,因为只有成员函数可以影响它们。

#2.某些东西的封装性与“当其内容改变时可能造成的代码破坏量”成反比。

(所谓改变,也许是从class中移除它(这或许有利于计算封装性))

#3.protected并不比public更具封装性。

理由:public成员变量供所有使用它的客户所使用,如果移除了它,所有使用

它的客户码都会被破坏,因此封装性为0, protected成员变量被所有derived

classes和class本身所使用,如果移除了它,所有使用它的客户码也都遭到

破坏,因此封装性也为0.

____________________________________________________________________________________________________________________________________

条款23:宁以 non-member, non-friend 替换 member 函数

#1.如果某些东西被封装,就会降低其可见性,愈多东西被封装,愈少人可以看见它。

而愈少人可以看见它,我们就有愈大的弹性去变化它,因为我们的改变仅仅影响那些

看到改变的人或事物。

#2.宁可拿 non-member, non-friend函数替换 member 函数。这样做可以增加

(1)封装性:

non-member、non-friend函数比member函数更缺少访问class中内部protected成员

和private成员的权限,换言之,能看到的更少,因此,封装性更好。

(注意,这里为什么不包括friend函数)

(2)包裹弹性:

non-member、non-friend函数的封装将class与该函数分离,减少了代码修改的

编译依存性,因此具有更高的包裹弹性。(说白了,就是与class更无关了)

(3)机能扩充性:

non-member、non-friend函数可以被置于与class相同的namespace内,如此一来

便可实现多组分别位于不同源文件的便利函数,从而实现了分离的扩充机能。

(namespace可以跨越多个源文件,而class不行)

例如:

//-----------------------------------
#include"WebBrowser.h"
namespace WebBrowserStuff{
class WebBrowser{...};
...        //核心机能,例如几乎所有客户所需要的 non-member函数
}
//-----------------------------------
#include"WebBrowserBookmarks.h"
namespace WebBrowserStuff{
...        //与书签相关的便利函数
}
//-----------------------------------
#include"WebBrowserCookies.h"
namespace WebBrowserStuff{
...        //与cookies相关的便利函数
}
//-----------------------------------
//此时若想再添加一组便利函数,只需要包含相关的头文件,便可以在 namespace
//中轻松的再添加,实现扩展机能。

<pre name="code" class="cpp">//=============================================================
// 以下是一个使用member函数和non-member、non-friend函数的示例:
//=============================================================
class WebBrowser{
public:
    ...
    void clearCache();  //清除下载元素缓存
    void clearHistory(); //清除访问过的URLs历史记录
    void removeCookies(); //移除系统中所有的Cookies
};
//若想将上述三个函数一同实现,可采用member()函数,和non-member、non-friend函数的方式,
//其中member()函数实现方式:
class WebBrowser{
public:
    ...
    clearEverything();   //调用clearCache,clearHistory,removeCookies函数
    ...
};
//non-member、non-friend函数实现方式:
void clearEverything(WebBrowser& wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

____________________________________________________________________________________________________________________________________

时间: 2024-10-05 15:24:23

Effective C++读书笔记(条款18-23)的相关文章

effective C++ 读书笔记 条款06

条款06:若不想使用编译器自动生成的函数,就该明确拒绝: 直接看代码与注释: #include <iostream> using namespace std; class Test { public: Test() { } ~Test() { } /* void TT() { Test t1; Test t2(t1); } */ private: Test(const Test& test); Test& operator = (const Test& test); }

Effective C++读书笔记(条款24-29)

Effective C++第四篇,扩展的有点多... (四).设计与声明 ____________________________________________________________________________________________________________________________________ 条款24:若所有参数皆需类型转换,请为此采用non-member函数 #1.如果你需要为某个函数的所有参数(包括被 this指针所指的那个隐喻参数)进行 类型转

Effective C++读书笔记(条款1-10)

不得不说,Effective C++确实是一本C++进阶的好书,刚浏览完第二遍, 现对其做一个大体性的总结 ,并进行适当的展开,作为以后C++参考复习之用. (一).让自己习惯C++ ____________________________________________________________________________________________________________________________________ 条款1:视C++为一个语言联邦 #1.将C++ 分

effective C++ 读书笔记 条款20

条款20:宁以 pass-by-reference-to-const 替换 pass -by -value 1:采用后者效率高:看代码: #include <iostream> using namespace std; class Person { public: Person() { cout<<"Person()"<<endl; } Person(const Person& p) { cout<<"Person(co

effective C++ 读书笔记 条款11

条款11: 在operator= 中处理"自我赋值" 在实现operator=时考虑自我赋值是必要的就像 x=y .我们不知道变量x与y代表的值是否为同一个值(把x和y说成是一个指针更恰当一点). 例如以下 第一版: #include <iostream> using namespace std; class bitmap { public: bitmap() { cout<<"调用bitmap()无參构造函数"<<endl; }

effective C++ 读书笔记 条款08

条款08  别让异常逃离析构函数: 假设在析构函数其中发生了异常,程序可能会过早结束或者导致不明白行为(异常从析构函数传播出去) 看代码: #include <iostream> using namespace std; class DBConnection { public: void close() { int i = 3; int j = 0; int k = i/j; printf("%d\n",k); } }; class DBConn { public: DBC

effective C++ 读书笔记 条款12与条款13

条款12:确定你的public继承塑膜出is-a关系: 这个条款主要将了一些特殊情况:比如企鹅是鸟,企鹅可以继承于鸟,但是鸟会飞,企鹅却不能飞:还有让正方形继承矩形可能也会造成这种尴尬! 这个问题以前想过,但是不知道怎么解决,如果现实生活当中确实要这么使用:比如 猫 狗 鱼  猪等等许多动物继承Animal类,但是猫狗等不会游泳, 假如这里是有很多动物,不能采用鱼里面专门加一个方法!  这个现在还没想出来,条款12也没有讲如果要这么用该怎么处理就是将要避免这样. is - a; 在面向对象程序设

effective C++ 读书笔记 条款10

条款10:  令operator= 返回一个reference to *this; 关于赋值,我们可以这样写: int  x,y,x; x =y = z; 这就是所谓的连续赋值 为了实现"连锁赋值"赋值操作符必须返回一个reference指向操作符的左侧实参.这是我们为class实现赋值操作符时应该遵循的协议: #include <iostream> using namespace std; class Widget { public: Widget() { cout<

Effective C++读书笔记(条款35-40)

(六).继承与面向对象设计 ____________________________________________________________________________________________________________________________________ 条款35:考虑virtual函数以外的其他选择 #1.virual函数的四个替代方案: (1).使用non-virtual interface(NVI)手法,那是 Template Method 设计模式