《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)-读书笔记

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

《Effective C++》第8章 定制new和delete-读书笔记



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

首先,有两个小点理由支持你将成员变量声明为private。

(1)接口的一致性。

如果public接口的都是函数,那么客户在调用时就不用考虑是否需要加小括号,因为每个调用的都是函数,必须加小括号。

(2)精细的访问控制。

使用函数可以让你对成员变量的处理有更精确的控制,如果你令成员变量为public,每个人都可以读写它。

最重要的是private提供封装性:

如果你通过函数访问成员变量,后面可以更改某个计算替换这个成员,而class客户一点也不会知道class的内部已经变化了,只需重新编译即可。

假设你将一个成员变量声明为public或protected而客户开始使用它,就很难改变那个成员变量所涉及的一切。太多代码需要重写、测试、重写文档、编译。从封装的角度,其实只有两种访问权限:private和其他(不提供封装)。

请记住:

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

(2)protected并不比public更具封装性。



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

下面有一个class:

class WebBrowser
{
public:
    void clearCache();
    void clearHistory();
    void removeCookies();
};

用户希望把这三个接口通过提供一个函数去做,可以定义一个成员函数调用这三个函数。

void WebBrowser::clearEverything()    //成员函数,调用clearCache、clearHistory和removeCookies
{
    clearCache();
    clearHistory();
    removeCookies();
}

当然,这个功能也可以通过一个非成员函数提供:

void clearBrowser(WebBrowser& wb)        //非成员函数
{
    wb.clearCache();
    wb.clearHistory();
    wb.removeCookies();
}

现在要考量的是哪一种做法比较好?

面向对象守则要求,数据以及操作数据的函数应该被捆绑在一起,这意味着它建议member是较好的选择。这个建议是错误的,是对面向对象真实意义的一个误解。

面向对象守则要求数据应该尽可能被封装。与直观相反,clearEverything带来的封装性比clearBrowser要低。因为non-member non-friend函数不能访问class内的private。

注意:这里考量的是member和non-member non-friend二者提供相同的机能时的一个抉择。friends对private的访问权利与member函数相同。



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

通常令class支持隐式类型转换是不好的,但也有例外。假设一个class表示有理数,那么允许整数“隐式转换”为有理数是比较合理的。

class Rational
{
public:
    Rational(int numerator = 0, int denominator = 1);
    int numerator() const;            //分子访问函数
    int denominator() const;        //分母访问函数
};

你想让这个class支持乘法运算。可能声明operator*为成员函数。

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

这个设计按照下面这种方式使用是没问题的:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);

Rational result = oneHalf * oneEighth;        //好的,没问题
result = result * oneEighth;                //好的,没问题

当你想使用混合运算时:

result = oneHalf * 2;            //好的,没有问题
result = 2 * oneHalf;            //错误,不满足交换律

本质上上面的用法与下面的等价:

result = oneHalf.operator*(2);        //result = oneHalf * 2;
result = 2.operator*(oneHalf);        //result = 2 * oneHalf;

所以你就明白为什么不满足交换律了。为了支持混合运算,需要将operator*设置为non-member函数。

const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}

下面的调用的OK的:

Rational oneFourth(1, 4);
Rational result;

result = oneFourth * 2;        //好的,没问题
result = 2 * oneFourth;        //好的,没问题

这里还要说明的一点是构造函数一定不能声明为explicit的,否则整型的数值2就不能隐式转换为Rational对象了

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



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

swap函数就是将两对象的值彼此赋予对方。缺省情况下,swap可由STL提供的swap算法完成。典型实现如下:

namespace std
{
    template<typename T>
    void swap(T& a, T& b)
    {
        T temp(a);
        a = b;
        b = temp;
    }
}

只要T支持拷贝(通过拷贝构造函数和copy assignment操作符完成),就可以利用该算法。

但存在某些情况,缺省的swap行为往往效率较低。例如,以指针指向一个对象。考虑下面的class:

class WidgetImpl
{
public:
...
private:
    int a, b, c;
    vector<double> v;
};

class Widget
{
public:
    Widget(const Widget& rhs);
    Widget& operator=(const Widget& rhs)
    {
        ...
        *pImpl = *(rhs.pImpl);
        ...
    }
private:
    WidgetImpl *pImpl;
};

如果我们要交换Widget对象,缺省的算法会拷贝3个Widget对象和3个WidgetImpl对象,效率很低。实际上,我们只需要置换pImpl指针的指向即可。

为了解决效率问题,我们需要告诉std::swap,当交换Widget对象时,只需要交换指针就好了。可以将std::swap针对Widget特化。

namespace std
{
    template<>
    void swap<Widget>(Widget& a, Widget& b)
    {
        swap(a.pImpl, b.pImpl);            //交换指针值
    }
}

这只是个思路,一般我们不能改变std内的任何东西,我们可以令Widget声明一个swap的public成员函数做置换工作,然后将std::swap特化。

class Widget
{
public:
    void swap(Widget& other)
    {
        using std::swap;                //
        swap(pImpl, other.pImpl);
    }
};

注意:成员swap函数绝不可抛出异常。因为swap的一个最好应用是帮助class提供强烈的异常安全性保障。

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



补充说明:条款25还有一些其他知识,我没有理解的特别好,就没有说明,但整个问题是由swap效率引发的,下次回顾时再补充吧。

时间: 2024-10-05 20:54:58

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

《Linux内核设计与实现》读书笔记——第一二章

<Linux内核设计与实现>读书笔记——第一二章 第一章 Linux内核简介 1.1 Unix的历史 简洁:仅提供系统调用并有一个非常明确的设计目的. 抽象:Unix中绝大部分东西都被当做文件,这种抽象使对数据和对设备的操作是通过一套相同的系统调用接口来进行的(open().read().write().lseek().close()). 可移植:使用C语言编写,使其在各种硬件体系架构面前都具备令人惊异的移植能力. 进程创建迅速:有独特的fork()系统调用,一次执行保质保量地完成一个任务.简

《Effective C++》第3章 资源管理(2)-读书笔记

章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(2)-读书笔记 <Effective C++>第3章 资源管理(1)-读书笔记 <Effective C++>第3章 资源管理(2)-读书笔记 <Effective C++>第8章 定制new和delete-读书笔记 条款1

《Effective C++》第3章 资源管理(1)-读书笔记

章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(2)-读书笔记 <Effective C++>第3章 资源管理(1)-读书笔记 <Effective C++>第8章 定制new和delete-读书笔记 内存只是你必须管理的众多资源之一.其他常见的资源还包括文件描述器(file des

《Linux内核设计与实现》读书笔记(八)- 中断下半部的处理

在前一章也提到过,之所以中断会分成上下两部分,是由于中断对时限的要求非常高,需要尽快的响应硬件. 主要内容: 中断下半部处理 实现中断下半部的机制 总结中断下半部的实现 中断实现示例 1. 中断下半部处理 那么对于一个中断,如何划分上下两部分呢?哪些处理放在上半部,哪些处理放在下半部? 这里有一些经验可供借鉴: 如果一个任务对时间十分敏感,将其放在上半部 如果一个任务和硬件有关,将其放在上半部 如果一个任务要保证不被其他中断打断,将其放在上半部 其他所有任务,考虑放在下半部 2. 实现中断下半部

《Java并发编程实战》第十一章 性能与可伸缩性 读书笔记

造成开销的操作包括: 1. 线程之间的协调(例如:锁.触发信号以及内存同步等) 2. 增加的上下文切换 3. 线程的创建和销毁 4. 线程的调度 一.对性能的思考 1 性能与可伸缩性 运行速度涉及以下两个指标: 某个指定的任务单元需要"多快"才能处理完成.计算资源一定的情况下,能完成"多少"工作. 可伸缩性: 当增加计算资源时(例如:CPU.内存.存储容器或I/O带宽),程序的吞吐量或者处理能力能相应地增加. 2 评估各种性能权衡因素 避免不成熟的优化.首先使程序正

《Linux内核设计与实现》读书笔记(十二)- 内存管理

内核的内存使用不像用户空间那样随意,内核的内存出现错误时也只有靠自己来解决(用户空间的内存错误可以抛给内核来解决). 所有内核的内存管理必须要简洁而且高效. 主要内容: 内存的管理单元 获取内存的方法 获取高端内存 内核内存的分配方式 总结 1. 内存的管理单元 内存最基本的管理单元是页,同时按照内存地址的大小,大致分为3个区. 1.1 页 页的大小与体系结构有关,在 x86 结构中一般是 4KB或者8KB. 可以通过 getconf 命令来查看系统的page的大小: [[email prote

《TCP/IP详解卷1:协议》第4章 ARP:地址解析协议-读书笔记

章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP:网际协议(1)-读书笔记 <TCP/IP详解卷1:协议>第3章 IP:网际协议(2)-读书笔记 <TCP/IP详解卷1:协议>第4章 ARP:地址解析协议-读书笔记 1.引言 当一台主机把以太网数据帧发送到位于同一局域网上的另一台主机时,是根据48 bit的以太网地址来确定目的接口的

《C++primer》v5 第3章 字符串、向量和数组 读书笔记 习题答案

3.1略 3.2 string str; //读行 while(getline(cin,str)) cout<<str<<endl; //读单个词 while(cin>>str) cout<<str<<endl; 3.3 输入运算符读到空白符结束 getline读到换行符结束,并丢弃换行符 3.4 比较大小. 比较大小是比较的第一个不相同的字符的大小. int main() { string a,b; cin>>a>>b;

《TCP/IP详解卷1:协议》第19章 TCP的交互数据流-读书笔记

章节回顾: <TCP/IP详解卷1:协议>第1章 概述-读书笔记 <TCP/IP详解卷1:协议>第2章 链路层-读书笔记 <TCP/IP详解卷1:协议>第3章 IP:网际协议(1)-读书笔记 <TCP/IP详解卷1:协议>第3章 IP:网际协议(2)-读书笔记 <TCP/IP详解卷1:协议>第4章 ARP:地址解析协议-读书笔记 <TCP/IP详解卷1:协议>第5章 RARP:逆地址解析协议-读书笔记 <TCP/IP详解卷1:协