1、使用命名空间特性会产生冗长的符号名,尤其是那些包含在几层嵌套命名空间中的符号。好在C++提供了使用using关键字,是的命名空间里的符号更加容易使用:

using namespace std;
string str("Look, no std::");

而更好的方式(因为它限定了引入全局命名空间的符号范围)是:

using std::string;
string str("Look, no std::");

然而,任何时候都不应该在公用API头文件的全局作用域内使用using关键字!这样做会导致所引用命名空间的全部符号在全局命名空间可见。这便破坏了使用命名空间的初衷。如果你希望在头文件中引用另一个命名空间的符号,应当使用完整限定名,例如:std::string。

2、“三大件”(Big Three)规则:析构函数、复制构造函数和赋值操作符这3个成员函数始终应该一起出现。

如果类分配了资源,则应该遵循“三大件”规则,同时定义析构函数、复制构造函数和赋值操作符。

3、为那些只接受一个参数的构造函数添加explicit是一种好的做法,用于阻止构造对象时特定的构造函数被隐式调用。

4、讨论将操作符定义为自由函数还是方法的最佳做法。

C++标准要求将下列操作符声明为成员方法,以确保它们接受左值作为其第一个操作数:

1> = 辅助;

2> [] 下标;

3> -> 类成员访问;

4> ->* 指针成员选择;

5> () 函数调用;

6> (T) 类型转换,即C风格的转换;

7> new/delete

其余的可重载操作符既可以定义为自由函数,也可以定义为类成员函数。站在良好API设计的角度,建议在定义操作符时尽量选择自由函数,而非类成员方法,原因如下。

1> 操作符对称性。如果二元操作符被定义为类的方法,它就必须有一个能用作左操作数的对象。以*操作符为例,这意味着用户能够编写类似currency*2这种表达式,(前提是已经定义了接受int类型的隐式构造函数或特定的*操作符。)但不能编写2*currency这种表达式,因为2.operator*(currency)没有意义。这破坏了用户期望的操作符交换性,即x*y应该和y*x等价。把*操作符声明为自由函数能带来很多益处。这样,如果没有显示声明构造函数,自由函数可以为左操作数和右操作数进行隐式类型转换。

2>降低耦合度。自由函数不能访问类的私有细节。由于它只能访问公有方法,因此降低了与类的耦合度。这是第2章所述的通用API设计理念:将不必访问私有成员或受保护的成员的类方法定义为自由函数,从而降低API的耦合度。

总结:除非操作符必须访问私有成员或受保护的成员,或它是=、[]、->、->*、()、(T)、new、delete其中之一,否则应该尽量将其声明为自由函数。

5、为类添加操作符

+=操作符会修改对象的内容,而我们知道所有的成员变量应该都是私有的,因此很可能需要将+=操作符定义为成员方法。另外,由于+操作符不会修改左操作数,从而也就不必访问私有成员,所以可以定义为自由函数。把这个操作符定义为自由函数还可以满足对称性的要求。实际上,+操作符可以通过+=操作符实现,这允许我们重用代码并且提供更加一致的行为。同时也减少了派生类需要重载的方法数量。

Currency operator + (const Currency& lhs, const Currency& rhs)
{return Currency(lhs) += rhs;
}

同样的技巧可以应用于其他算术操作符,比如-、-=、*、*=、/以及/=。例如,*=可以实现成员函数,而*可以使用*=操作符实现为自由函数。

至于关系操作符==、!=、<、<=、>及>=,必须实现为自由函数以保证对称性。在Currency类这个例子中,可以使用公有的GetValue()方法实现这些操作符。但如果这些操作符还需要访问对象的私有成员,那么有一种方法可以解决这个矛盾。这种情况下,可以提供公有方法来测试等于和小于条件,比如IsEqualTo()和IsLessThan()。进而可以用这两种基本函数实现所有关系操作符。

bool operator == (const Currency& lhs, const Currency& rhs)
{return lhs.IsEqualTo(rhs);
}bool operator != (const Currency& lhs, const Currency& rhs)
{return !(lhs == rhs);
}bool operator < (const Currency& lhs, const Currency& rhs)
{return lhs.IsLessThan(rhs);
}bool operator <= (const Currency& lhs, const Currency& rhs)
{return !(lhs > rhs);
}bool operator > (const Currency& lhs, const Currency& rhs)
{return rhs < lhs;
}bool operator >= (const Currency& lhs, const Currency& rhs)
{return rhs <= lhs;
}

最后考虑的操作符是<<, 我会把它用于流的输出(而非移位)。流操作符应该声明为自由函数,因为第一个参数是流对象。你也可以使用公有的GetValue()方法达到此目的。如果流操作符需要访问类的私有成员,那么可以为<<创建公有的ToString()方法供其调用,从而避免使用友元。

6、转换操作符

转换操作符用于定义将对象自动转换成不同类型的对象。典型的例子是定义定制的字符串类,它可以传递给接收const char*指针的函数,比如C标准库函数strcmp()和strlen()。

class MyString
{
public:MyString(const char* string);//把MyString转换成C风格的字符串operator const char* () { return mBuffer; }
private:char* mBuffer;int mLength;
};MyString mystr("Haggis");
int same = strcmp(mystr, "Edible");
int len = strlen(mystr);

注意,转换操作符没有指定返回值类型。这是因为编译器可以根据操作符名字推断出类型,而且转换操作符没有参数。

总结:给类添加转换操作符,从而利用自动类型强制转换。

7、避免使用#define定义常量

#define预处理指令本质上是用一个字符串替换源码中的另一个字符串。例如:

#define SETUP_NOISE(i, b0, b1, r0, r1) \t = vec[i] + 0x1000; \b0 = (lltrunc(t)) & 0xff; \b1 = (b0 + 1) & 0xff; \r0 = t - lltrunc(t); \r1 = r0 - 1.f;

无论如何都不应该在公有API头文件中用这种方式使用#define, 因为它会泄露实现细节。如果想在.cpp文件中使用这一技巧,并且理解#define的所有特性,那么尽管去做,但是绝不要在公有头文件中这样做。

剩下的问题是使用#define为API指定常量,比如:

#define MORPH_FADEIN_TIME  0.3f
#define MORPH_IN_TIME      1.1f
#define MORPH_FADEOUT_TIME 1.4f

应该避免采用#define的这种用法(当然,除非你正在编写纯C API),原因如下所述。

1>没有指定类型。#define不涉及为定义的常量做任何类型检查。因此必须确保显示地指定定义的常量类型,以避免歧义,比如对单精度浮点型常量使用f后缀。如果将一个浮点常量定义为10, 那么某些情况下它会被假定为整型,继而引起意料之外的数学取整错误。

2>没有指定作用域。#define语句是全局的,不会被限制于诸如单个类的特定作用域。可以使用#undef预处理指令取消之前的#define,但这对于声明客户能够使用的常量而言没有什么意义。

3>没有访问控制。不能把#define标记为公有的、受保护的或私有的。它本质上是公有的。因此不能使用#define指定一个只能被你定义的某个基类的派生类访问的常量。

4>没有符号。前面给出的例子中,像MORPH_IN_TIME这样的符号名可能会被预处理器从代码中剥离,这样,编译器就无法看见这个名字,也就不能将其加入符号表。当客户试图调试使用API的代码时,这可能会隐藏一些重要信息,因为客户在调试器中只能看到一些没有任何描述性名字的常量值。

与使用#define声明API常量相比,更可取的办法是声明const常量,虽然定义const变量在某种程度上可能会使客户代码更臃肿。这里仅给出#define示例的另一个更好的版本:

class Morph
{
public:static const float FadeInTime;static const float InTime;static const float FadeOutTime;...
};

这些常量的实际值在关联的.cpp文件中指定。(如果确实想让用户知道这些常量的值,可以在Morph类的API文档中告知这些信息。)注意,这种表示法不存在前面提到的任何问题:常量被表明为浮点型,作用域在Morph类中,显示标记为公有访问,并且会在符号表中生成表项。

总结:使用静态const数据成员而非#define表示类常量。

#define的另一个用途是为给定的变量提供一系列可能的值。例如,

#define LEFT_JUSTIFIED    0
#define RIGHT_JUSTIFIED   1
#define CENTER_JUSTIFIED  2
#define FULL_JUSTIFIED    3

可以通过enum关键字定义枚举类型,以便更好地表达这一语义。使用枚举使得类型更加安全,因为此时编译器会使用符号名而不是直接以整数设定枚举值(除非把int显示转换成枚举类型)。这也让传递非法值更加困难,比如在前面给出的例子中无法传递-1或者23。可以把前面的#define定义代码转换成枚举类型,如下所示:

enum JustificationType {LEFT_JUSTIFIED,RIGHT_JUSTIFIED,CENTER_JUSTIFIED,FULL_JUSTIFIED
};

[读书笔记] -《C++ API设计》第6章 C++用法相关推荐

  1. 【读书笔记】.NET本质论第四章-Programming with Type(Part Two)

    欢迎阅读本系列其他文章: [读书笔记].NET本质论第一章 The CLR as a Better COM [读书笔记].NET本质论第二章-Components(Part One) [读书笔记].N ...

  2. 大数据之路读书笔记-10维度设计

    大数据之路读书笔记-10维度设计 文章目录 大数据之路读书笔记-10维度设计 10.1 维度设计基础 10.1.1 维度的基本概念 10.1.2 维度的基本设计方法 10.1.3 维度的层次结构 10 ...

  3. 《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第五章 着色基础 Shading Basics

    写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了.不对之处甚多,以后理解深刻了,英语好了再回来修改.相信花在本书上的时间和精力是值得的. -- ...

  4. 《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第七章 阴影 Shadows

    写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了.不对之处甚多,以后理解深刻了,英语好了再回来修改.相信花在本书上的时间和精力是值得的. -- ...

  5. 《Real-Time Rendering 4th Edition》读书笔记--简单粗糙翻译 第六章 纹理 Texturing

    写在前面的话:因为英语不好,所以看得慢,所以还不如索性按自己的理解简单粗糙翻译一遍,就当是自己的读书笔记了.不对之处甚多,以后理解深刻了,英语好了再回来修改.相信花在本书上的时间和精力是值得的. -- ...

  6. C++ API 设计 06 第一章 简介

    第一章 简介 1.1 应用程序编程接口是什么? 应用程序编程接口(API)提供对问题的一个抽象,并说明客户端如何与实现这个问题的解决方案的软件组件来进行交互. 这些组件本身通常作为一个软件库发布,允许 ...

  7. 读书笔记-流畅的python(1-6章)

    前言:这正是本书的主要目的:着重讲解这门语言的基本惯用法,让你的代码简洁.高效且可读,把你打造成熟练的 Python 程序员. 自己总结学习作为输出,很多为了节省时间只是复制粘贴,不具有广泛意义 第一 ...

  8. C++ API 设计 07 第二章

    第二章 品质 本章的目标是回答下面的问题:一个良好的API需要什么样的品质?大多数开发人员都同意,一个良好的API应该设计得很优雅,而且仍然非常容易使用.它应该在后台运行且使用起来让你觉得很惬意.这些 ...

  9. 【Java编程思想】读书笔记(二)第六章---第十章

    Java编程思想(第四版)学习笔记 第六章---第十章 第六章:访问权限控制 6.2Java访问权限修饰词 第七章:复用类 7.1 组合语法 7.2 继承语法(extends) 7.4.2名称屏蔽(重 ...

  10. 【读书笔记】推荐系统实践·第四章·利用用户标签数据

    代码方面,主要实现了4.3和4.2.2的一个验证统计,4.4的代码本来准备写一下的,后来因为杂碎的统计工作太多就放弃了.代码和笔记的word版放在https://github.com/littleli ...

最新文章

  1. foolscap实现rpc(四)
  2. Confluence 6 Windows 中以服务方式自动重启的原因
  3. docker基础知识之挂载本地目录
  4. 【三分钟学习FFMPEG一个知识点】FFMPEG关于avio_alloc_context申请使用内存释放问题
  5. 了解恶意软件和插件!
  6. count返回0_你是一直认为 count(1) 比 count(*) 效率高么?
  7. java中datetime类型转换,Java中日期格式和其他类型转换详解
  8. 【数据库】Oracle用户、授权、角色管理
  9. 修改小程序swiper 点的样式_高质量的微信小程序样式模板应该长什么样?
  10. 写在前面--点燃酱爆心中的那团火
  11. 阳江口碑好的java培训价格
  12. angular要多久学会_成为优秀Angular开发者所需要学习的19件事
  13. pom.xml mvn package expected START_TAG or END_TAG not TEXT
  14. java图片像素90翻转_java后台解决上传图片翻转90的问题,有demo,经过测试可用...
  15. DataMatrix 码提取流程
  16. 联想计算机电源风扇怎样清理,电脑怎么清灰加硅脂 联想笔记本清灰换硅脂图文教程...
  17. 大地高、正高和正常高
  18. Win11电脑摄像头打开看不见,显示黑屏如何解决?
  19. VisionMobile:Apple和三星利润的秘诀
  20. 【TypeScript介绍】一文带你初步了解TypeScript

热门文章

  1. java socket 工具_java Socket简易聊天工具
  2. python判断、创建文件夹
  3. 用计算机和用纸的区别,英语四级机考与传统纸考的主要区别
  4. linux结构体大小端,【转】位结构体+大小端模式
  5. Java5~11新特性
  6. oracle9i的erp数据库无法正常关闭的解决方法。
  7. 尚学堂-马士兵-专题-正则表达式
  8. JAVA中运用数组的四种排序方法
  9. C++ 同步/异步与阻塞/非阻塞的区别
  10. matlab中的种子数seed,set.seed()设置种子到底是啥作用?