Effective C++笔记04:设计与声明

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

1,好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。

2,“促进正使用”的办法包括接口的一致性,以及与内置类型的行为兼容。

3,“阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。

4,shared_ptr支持定制型删除器。这可以防范DLL问题,可以用来自动解除互斥锁。

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

博客地址:http://blog.csdn.net/cv_ronny转载请注明出处!

如何设计你的类:

1,新type的对象应该如何创建和销毁?

影响到的设计函数:构造函数、析构函数以有内存分配函数和释放函数(operator new,operator new[],operator delete,operator delete [])。

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

取决于构造函数和赋值操作符的行为。

3,新type对象如果被passed-by-value,意味着什么?

拷贝构造函数用来定义一个type的pass-by-value该如果实现。

4,什么是新type的“合法值”?

构造函数必须进行有效的检测。

5,你的新type需要配合某个继承图系吗?

如果继承自其他的类,要受到其他类的束缚,注意virtual和no-virtual的影响。如果有其他类继承这个类,那么需要考虑是否要把析构函数设计为virtual函数。

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

如要希望其他类型能转换为你所设计的type类型,则需要对应的non-explicit构造函数存在。如果需要你把设计的type可以转换为其他类型,则需要定义类型转换函数operator T。

7,什么样的操作和函数对此新type而言是合理的。

取决于你为你的class声明哪些函数。其中有些可能是成员函数,有些则不是。

8,什么样的标准使函数应该驳回?

那些正是你必须声明为private者。

9,谁该取用新type的成员?

这个提问帮组你决定哪个成员为public,哪个为protected,哪个为private。它也帮助你决定哪一个class或function应该是友元,以及将它们嵌套于另一个之内是否合理。

10,你的新type有多么一般化?

如果你定义的是一整个types,是否应该定义一个新的class template 。

11,什么是新type的“未声明接口”?

12,你真的需要一个新type吗

如果只是定义新的derived class以便为既有的类添加机能,那么说不定单纯定义一个或多个非成员函数或模板,更能够达到目标。

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

pass-by-value会造成较多的构造函数与析构函数的开销,并且在将派生类传递给基类接口的时候会发生类的切割问题。

上面的规则并不适用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-vaule往往比较适当。

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

绝对不要返回point或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回point或reference指向一个local static对象而有可能同时需要多个这样的对象。

我们来考虑一个有理数的类以及为它定义一个有理数想乘的友元:

// 博客地址:http://blog.csdn.net/cv_ronny
class Rational{
public:
    Rational(int numerator = 0, int denominator = 1);
private:
    int n, d;
    friend const Rational operator*(const Rational& lhs, const Rational& rhs);
};

现在我们来设计这个友元函数,它的功能是返回两个Rational对象的乘积,我们有3种方案:

// 方案一
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
    return result;
}

这个函数返回了result的引用,但是result是一个local对象,调用函数结束时,该对象会被销毁,而它的引用也就毫无意义指向了未定义的对象。

// 方案二
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    Rational* result=new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
    return *result;
}

方案中result并不是一个local对象,而是一个从heap中动态分配得到的对象的指针,那么这样的问题是谁来负责delete这个指针呢,假如有这样的表达式:

Rational w, x, y, z;
w = x*y*z;

这面的表达式其实调用了两次operator*操作,也就是创建了两次动态内存区域,但是没有办法取得它们的指针。

// 方案三
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
    static Rational result;
    result = Rational(lhs.n*rhs.n, lhs.d*rhs.d);
    return result;
}

这次是通过static对象使得result脱离函数后依然存在,但是就像所有的static对象设计一样,这一个也立刻造成我们对多线程安全性的疑虑。而且如果有下面的代码:

bool operator==(const Rational& lhs, const Rational& rhs);
Rational a, b, c, d;
if ((a*b) == (c*d)){}
else{}

上面代码里的if后的条件总是成立的,因为a*b返回的一个static对象的引用,而c*d返回的是同一个static对象的引用,所以永远相等。

所以,我们最终的选择是通过pass-by-value来返回新的对象。

const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs.n*rhs.n, lhs.d*rhs.d);
}

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

首先是代码的一致性(调用public成员时不用考虑是成员还是函数)。

其次封装性,都写成函数进行访问可以提供以后修改访问方法的可能性,而不影响使用方法。另外,public影响的是所有使用者,而protected影响的是所有继承者,都影响巨大,所以都不建议声明成员变量。

切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺条件获得保证,并提供class作者以充分的实现弹性。

protected并不比public更具封装性。

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

想像有个class用来表示网页浏览器。这样的class可能提供的众多函数中,有一个用来清除下载元素的高速缓冲区(cache of downloaded elements)、清除访问过的URLs的历史记录、以及移除系统中所有的cookies。

class WebBrowse
{
public:
    void clearCache();
    void clearHistroy();
    void removeCookies();
};

许多用户想一个函数来执行整个动作,因些WebBrowse也提供这样一个函数:

class WebBrowse
{
public:
    //...
    void clearEverything();//依次调用clearCache(),clearHistory(),removeCookies()
};

当然这个功能也可以由一个non-member函数来完成:

void clearEverything(WebBrowse& wb)
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

那么哪一个比较好呢?

根据面向对象守则要求,数据以及操作的那些函数应该捆绑在一块,这意味着member函数是较好的选择。不幸的是这个建议不正确。面向对象要求数据应该尽可能被封装。

member函数带来的封闭性比non-member函数低,因为它并不增加“能够访问class内之private成份”的函数数量。

此外,提供non-member函数可以允许对WebBrowse相关机能能有较大的包裹弹性,而那最终导致较低的编译相依度,增加WebBrowse的可延伸性。

如果我们把WebBrowse相关的功能设计为non-member函数,那么可以将其功能相关的函数的声明单独放在一个头文件中,然后在一个命名空间下。这时候如果我们相扩展相关的这些功能,只需要像其他功能函数一样把声明入在头文件里即可。

而这种切割方式并不适用于class成员函数,因为一个class必须整体定义,不能被切割为片断。

请记住

宁可拿non-member non-friend函数替换member函数。这样做可以增加封装性、包裹弹性和机能扩充性。

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

当我们为一个有理数类的设计一个乘法操作符的重载函数,如果我们把它作为类的成员:

class Rational
{
public:
    //...
    const Rational operator*(const Rational &lhs);
};

当我们尝试混合算术的时候,你会发现只有一半行得通:

Rational result, oneHalf;
result = oneHalf * 2;
result = 2 * oneHalf;

上面第二个赋值语句是错误的,因为它等价于result=2.operator*(oneHalf),这显然是错误的。

而第2条语句等价于result=oneHalf.operator(2),它可以执行成功是因为2发生了隐式类型转换,因为Rational有一个non-explicit的接受int形参的构造函数。

所以,如果我们想执行上面的操作,我们需要将这个重载函数设计为non-member函数。

const Rational operator*(const Rational& lhs, const Rational& rhs);

如果你需要为某个函数的所有参数(包括被this指针所指向的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。

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

1,当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常。

2,如果你提供一个member swap,也该提供一个non-member swap来调用前者,对于classes(而非templates),也请特化std::swap。

3,调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。

4,为“用户定义类型”进行std template全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。

Effective C++笔记04:设计与声明

时间: 2024-08-03 14:15:07

Effective C++笔记04:设计与声明的相关文章

《Effective C++》第4章 设计与声明(2)-读书笔记

章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(2)-读书笔记 <Effective C++>第3章 资源管理(1)-读书笔记 <Effective C++>第3章 资源管理(2)-读书笔记 <Effective C++>第4章 设计与声明(1)-读书笔记 <Eff

Effective c++(笔记) 之 类与函数的设计声明中常遇到的问题

1.当我们开始去敲代码的时候,想过这个问题么?怎么去设计一个类? 或者对于程序员来说,写代码真的就如同搬砖一样,每天都干的事情,但是我们是否曾想过,在c++的代码中怎么样去设计一个类?我觉得这个问题可比我们"搬砖"重要的多,大家说不是么? 这个答案在本博客中会细细道来,当我们设计一个类时,其实会出现很多问题,例如:我们是否应该在类中编写copy constructor 和assignment运算符(这个上篇博客中已说明),另外,我们是让编写的函数成为类的成员函数还是友元还是非成员函数,

Effective C++笔记06:继承与面向对象设计

关于OOP 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 1,继承可以是单一继承或多重继承,每一个继承连接可以是public.protected或private,也可以是virtual或non-virtual. 2,成员函数的各个选项:virtual或non-virtual或pure-virtual. 3,成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响C++的名称查找规则?设计选项有如些?如果class的行为

Effective c++(笔记)之继承关系与面向对象设计

1.公有继承(public inheritance) 意味着"是一种"(isa)的关系 解析:一定要深刻理解这句话的含义,不要认为这大家都知道,本来我也这样认为,当我看完这章后,就不这样认为了. 公有继承可以这样理解,如果令class D以public 的形式继承了class B ,那么可以这样认为,每一个类型为D的对象同时也可以认为是类型为B的对象,但反过来是不成立的,对象D是更特殊化更具体的的概念,而B是更一般化的概念,每一件事情只要能够施行于基类对象身上,就一定可以应用于派生类对

《Effective C++》 读书笔记之四 设计与申明

<Effective C++> 读书笔记之四 设计与申明 条款18:让接口容易被正确使用,不易被误用. 重点: 好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. "促进正确使用"的办法包括接口的一致性,以及与内置类型的行为兼容. "阻止误用"的办法包括建立新类型.限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. tr1::shared_ptr支持定制型删除器.这可防范DLL问题,可被用来自动解除互斥锁等等. 20

《Effective C++》设计与声明:条款18-条款19

软件设计是"让软件做你想让它做的事情"的步骤和做法.从一般性的构想开始,逐渐清晰,构造细节,最终设计出良好的接口(interface).这些接口而后变为C++的声明. 下面讲的是关于接口设计和声明的做法.设计接口一个很重要的准则是:让接口容易被正确使用,不容易被误用.这是一个大的准则,细化之后包括很多内容:正确性.高效性.封装性.维护性.延展性以及协议的一致性. 条款18:让接口容易被正确使用,不容易被误用 接口是客户和你的代码交换的唯一手段.如果客户正确使用你开发的接口,那自然很好:

Effective C++笔记:构造/析构/赋值运算

条款05:了解C++默默编写并调用哪些函数 默认构造函数.拷贝构造函数.拷贝赋值函数.析构函数构成了一个类的脊梁,只有良好的处理这些函数的定义才能保证类的设计良好性. 当我们没有人为的定义上面的几个函数时,编译器会给我们构造默认的. 当成员变量里有const对象或引用类型时,编译器会不能合成默认的拷贝赋值函数:当一个基类把它的拷贝赋值函数定义为private时,它的派生类也不无生成默认的拷贝赋值函数,因为它无法完成基类成份的赋值. 条款06:若不想使用编译器自动生成的函数,就该明确拒绝 将拷贝构

Effective C++笔记05:实现

条款26:尽可能延后变量定义式的出现时间 博客地址:http://blog.csdn.net/cv_ronny 转载请注明出处! 有些对象,你可能过早的定义它,而在代码执行的过程中发生了导常,造成了开始定义的对象并没有被使用,而付出了构造成本来析构成本. 所以我们应该在定义对象时,尽可能的延后,甚至直到非得使用该变量前一刻为止,应该尝试延后这份定义直到能够给它初值实参为止. 这样做的好处是:不仅可以避免构造(析构)非必要对象,还可以避免无意义的default构造行为. 遇到循环怎么办?此时往往我

Effective c++(笔记)----类与函数之实现

上篇博客中集中说明了在设计一个类的时候常遇到的问题,当然博客中还夹杂着我随时想到的一些知识,发现自己写博客没很多人写的好,可能是自己语言不会组织,要么就是写的东西大家不愿意看,反正是有这方面的专业问题或者博客中有什么明显的错误和问题,大家提出来,我也好改进哈! 回归正题,这篇博客就大概的把Effective c++中类与函数这节看到的知识点做个笔记. 设计好一个类后,自己就要去实现这个类(实现类中的成员函数.友元.非成员函数等) 可能大家会遇到以下问题 1.在类的成员函数中,尽量避免返回内部数据