章节回顾:

《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++》第5章 实现-读书笔记

《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 = , int denominator = );
int numerator() const; //分子访问函数
int denominator() const; //分母访问函数
};

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

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

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

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

当你想使用混合运算时:

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

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

result = oneHalf.operator*();        //result = oneHalf * 2;
result = .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(, );
Rational result; result = oneFourth * ; //好的,没问题
result = * 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效率引发的,下次回顾时再补充吧。

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

  1. effective java 第2章-创建和销毁对象 读书笔记

    背景 去年就把这本javaer必读书--effective java中文版第二版 读完了,第一遍感觉比较肤浅,今年打算开始第二遍,顺便做一下笔记,后续会持续更新. 1.考虑用静态工厂方法替代构造器 优 ...

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

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

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

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

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

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

  5. 《Effective C++》第1章 让自己习惯C++-读书笔记

    章节回顾: <Effective C++>第1章 让自己习惯C++-读书笔记 <Effective C++>第2章 构造/析构/赋值运算(1)-读书笔记 <Effecti ...

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

    <Linux内核设计与实现>读书笔记--第一. 二章 标签(空格分隔): 20135321余佳源 第一章 Linux内核简介 1.Unix内核特点 十分简洁:仅提供几百个系统调用并且有明确 ...

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

    <Linux内核设计与实现>读书笔记——第一二章 第一章 Linux内核简介 1.1 Unix的历史 简洁:仅提供系统调用并有一个非常明确的设计目的. 抽象:Unix中绝大部分东西都被当做 ...

  8. 《Linux内核设计与实现》读书笔记——第五章

    <Linux内核设计与实现>读书笔记--第五章 标签(空格分隔): 20135321余佳源 第五章 系统调用 操作系统中,内核提供了用户进程与内核进行交互的一组接口.这些接口让应用程序受限 ...

  9. 《Linux内核设计与实现》读书笔记(十七)- 设备与模块

    本章主要讨论与linux的设备驱动和设备管理的相关的4个内核成分,设备类型,模块,内核对象,sysfs. 主要内容: 设备类型 内核模块 内核对象 sysfs 总结 1. 设备类型 linux中主要由 ...

随机推荐

  1. 关于hashmap 与concurrentHashMap

    hashmap是不安全的,要实现安全,可以用Collections里面的synchronizedMap包裹来实现安全,或者用concurrentMap, 注意:hashtable是安全的 从JDK1. ...

  2. cordova 插件

    1.移动端WebApp开发,如何实现状态栏沉浸式效果? cordova-plugin-fullscreen 2. cordova热更新插件-不发布应用市场动态更新APP源码 https://githu ...

  3. RN初始化环境快速配置

    1.安装node node是基于js的,node.js轻量级的Web服务器,想要是React Native跑起来需要node环境,可以去官网下载安装node.js 下载地址:https://nodej ...

  4. MR案例:分区和排序

    现有一学生成绩数据,格式如下:<学号,姓名,学院,成绩>  //<id, name, institute, grade>. 需求描述:查询成绩大于等于60分的学生数据,按学院分 ...

  5. 解决java.lang.IllegalArgumentException: Can't load standard profile: LINEAR_RGB.pf

    调用org.apache.batik.transcoder.Transcoder转换图片时,报了以下错误:java.lang.IllegalArgumentException: Can't load ...

  6. 搭建springmvc

    对于SpringMvc来说,搭建这框架,首先引入相应的包.如下: <project xmlns="http://maven.apache.org/POM/4.0.0" xml ...

  7. Dijkstra+优先队列

    /* Dijkstra的算法思想: 在所有没有访问过的结点中选出dis(s,x)值最小的x 对从x出发的所有边(x,y),更新 dis(s,y)=min(dis(s,y),dis(s,x)+dis(x ...

  8. rocketmq事务消息

    rocketmq事务消息 参考: https://blog.csdn.net/u011686226/article/details/78106215 https://yq.aliyun.com/art ...

  9. [小问题笔记(三)] SVN树冲突(Tree Conflict),文件不能提交的解决办法

    传说中SVN的树冲突是由不同开发者删除文件,移动文件神马的造成的. 我们遇到的情况是: 开发人员小B移动了项目中几个文件然后提交.开发人员小L更新项目至最新版本. 获取到移动后的文件则显示文件已被修改 ...

  10. redis_port.py

    !/usr/bin/env python import os import json import simplejson as json t=os.popen("""su ...