effective c++18-25条款“接口设计与声明”整理

一、让接口容易被正确使用,不易被误用

接口设计的原则是,方便日后和其他用户的使用,不要把问题留给接口使用者

(1)用常规的用法调用“特别”设计的接口。所以需要尽可能的把自己的设计往常规上靠:数据对象的行为要尽可能符合内建对象(比如int)的行为;接口的名字和意义要尽可能一致(比如STL中的容器基本都有一个叫做size的返回容器大小的接口)……这样做鼓励用户去正确的看待和使用你的接口。

(2)忘了处理调用接口后的遗留问题。因此不要让用户去“记得”做一些事情。

如设计一个接口返回一个指向某新建对象的指针,该接口的用户需要“记得”去释放这个指针所指的对象:如果用户忘了释放或释放了好几次,后果SB了

解决的办法之一是让该接口返回一个智能指针,这样用户用完了就可以“忘记”这个指针:它自己会处理后事。

(3)所谓的“跨DLL问题”(cross DLL problem):在一个DLL中new一个对象,然后对象被传到另外一个DLL里被delete。大师推荐用shared_ptr因为它解决了这个问题。

代价:额外对象的创建和销毁需要时间空间。比如boost的shared_ptr就是普通指针的两倍大小,还有额外的对象操作时间+过程动态内存分配等。

二、设计class犹如设计type

(1)小心设计类的创建和销毁方式,及构造函数和析构函数。

(2)认真考虑如何区分类的构造函数和copy构造函数、赋值(assignment)操作符。即初始化与赋值的差别。

(3)注意实现类的传值(passed by value)。这个实际上是在说要注意拷贝构造函数的实现。

(4)需要审视类所在的继承体系。

如果该类有父类,那么必定要受到父类的一些限制,特别是函数是否为虚构;如果该类有子类,那么就要考虑是不是一些函数需要定义为虚函数,比如说析构函数。

(5)谨慎实现类对象与其他类型对象的转换。这一点稍有些复杂:如果有将T1转换为T2的需求,就有隐式转换和显式转换两种方式。

对于前者,可以编写一个(隐式的)转换函数,或者是通过额外编写一个T2的构造函数来实现T1向T2的转换。

对于后者,Scott说写一个(显式的)转换函数就可以了。

(6)·需要考虑该类需要参与哪些运算。很明显,如果需要参与A运算就要相应定义类的A运算符函数。大师在这里提的另外一点是,这些运算符号函数有些应该是成员函数,有些不应该。

(7)不要提供不应该暴露的标准函数。这里的标准函数指的是构造/析构/拷贝等等可能由编译器“自愿”为你生成的函数,如果不希望它们中的一些被外界调用,就声明为私有(private)。

(8)注意设计类成员的访问权限。公有(public)、保护(protected)、私有(private)应该用哪一种?有没有需要定义友元?或者是干脆来一个类中类?都需要考虑。

三、pass by referrence to const 替换 pass by value

C++传递对象的时候默认是传值的(pass-by-value),而这样的传递自然是昂贵的:这当中包含了临时对象的构造/析构,以及临时对象中的对象的构造/析构,运气背点还可能有对象中的对象中的对象的构造/析构。

(1)相对于传“值”,一个更好的替代方法是传“const引用”(pass-by-reference-to-const)。

(2)传值与传指针的一个区别是,通过传值传递的对象并不是原来的对象,而是一个复制品,所以随便你打它骂它,真身都不会受到影响。

(3)而通过传指针的对象和原来的对象就是同一家伙,改动一个另外一个也受到相同的影响。而这有时候并不是我们想要的结果。

(4)考虑到传值代价太高,传“const引用”就成了一个很好的替代品。

(5)传“const引用”的另外一个好处在于避免了“剥皮问题”(slicing problem,侯捷大师的版本是“对象切割问题”,我用这个中文名字是为了更容易记住:))

用传值方式传参的函数,如果某参数的类型是一个父类对象,而实际传递的参数是一个子类对象,只有该对象的父类部分会被构造并传递到函数中,子类部分的成员,作为父类对象的“皮”,就被血淋淋的剥掉了……

而如果用传“const引用”方式,就没有这种惨无人道的状况:本来父类的指针就可以用来指向一个子类对象,天经地义。

例外:对于内置类型(bulit-in type)对象以及STL中的迭代器、函数对象,Scott还是建议使用传值方式传递,原因是他们本来就是被设计成适合传值传递的。

四、该换回对象时别返回它的reference

如果一个函数可能返回一个对原来不存在的对象的引用,那么函数就要自己去创建这个对象:要么在栈上(stack)要么在堆上(heap)。

(1)第一种情况中,函数中定义了局部对象,然后返回对该对象的引用。对象在函数结束后自动销毁,引用指向无效的地址。

(3)第二种情况,函数使用new动态创建了一个对象,然后返回对该对象的引用。粗看没有问题,因为这个返回的引用还是有效的。

但是细想就会发现:我们能确保这个对象被正确的收回(delete)吗?

反思:在类中重载运算符,默认以该类的对象调用其重载操作符,返回对改已经存在对象的引用没什么问题,所以与该条款描述的不符。

五、将成员变量声明为private

公有的成员对类的外部完全开放,而保护的成员对类的继承者完全开放。从封装的角度,只有两种访问级别:私有,及其他。其他不多说。

六、 用 non-member、non-friend 替换 member 函数

相比较Java和c#,C++允许类外定义非成员函数,存在即合理,必要时可以祭出

从灵活性上来说,非成员函数更少编译依赖(compilation dependency),也就更利于类的扩展。

例:一个类可能有多个成员函数,可能有一个函数需要A.h,另外一个函数要包含B.h,那么在编译这个类时就需要同时包含A.h和B.h,也就是说该类同时依赖两个头文件。

如果使用非成员函数,这个时候就可以把这些依赖关系不同的函数分别写在不同的头文件中,有可能这个类在编译时就不需要再依赖A.h或是B.h了。

把这些非成员函数分散定义在不同头文件中的同时,需要用namespace关键字把它们和需要访问的类放在一起。

// code in class_a.h
  namespace  AllAboutClassA  {
     class  ClassA  { // ..};
     // ..
  }

  // code in utility_1.h
  // ..
  namespace  AllAboutClassA  {
     void  WrapperFunction_1() { // ..};
     // ..
  }
  // ..

  // code in utility_2.h
  // ..
  namespace  AllAboutClassA  {
     void  WrapperFunction_2() { // ..};
     // ..
  }
  // ..

如果有需要添加新的非成员函数,我们要做的只是在相同的名字空间中定义这些函数就可以,那个类丝毫不会被影响,也即所谓的易扩展性吧。

对于类的用户来说,这样的实现方式(指用非成员函数)自由度更大

时间: 2024-08-29 09:17:54

effective c++18-25条款“接口设计与声明”整理的相关文章

《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++笔记(四):设计与声明

参考:http://www.cnblogs.com/ronny/p/3747186.html 条款18:让接口容易被正确使用,不易被误用 1,好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2,“促进正使用”的办法包括接口的一致性,以及与内置类型的行为兼容. 3,“阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. 4,shared_ptr支持定制型删除器.这可以防范DLL问题,可以用来自动解除互斥锁. 条款19:设计cl

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

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

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

(一) 看下面这个例子: class Date { public: Date(int month, int day, int year); }; 很有可能引起下面这两个错误: (1)他们也许会以错误的次序传递参数,如:Date d(30, 3, 1995); (2)他们可能传递一个无效的月份或天数,如:Date d(2, 30, 1995); 许多像这类客户端错误. 解决方法:简单的外覆(wrapper types)类型来区别天数.月份.和年份,然后于Date构造函数中使用这些类型: struc

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

条款18:让接口容易被正确使用,不易被误用 在(参数)类型上客户不知道怎么使用的时候,可以导入简单的"外覆"类型来区别参数.也就是,自定义数据类型,使客户明确调用相关的类型,防止误用. 尽量让自定义类型的行为和内置类型的行为相同,因为客户会想当然的和使用内置类型一样使用自定义类型,这也就是上面说的让接口容易被正确的使用.STL容器的接口十分一致,这也是他们非常容易使用的一个原因. 任何接口如果要求客户必须记得做某些事情,那么就有着"不正确的使用"的倾向,因为客户可能

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

条款18:让接口容易被正确使用,不易被误用 1,好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2,"促进正使用"的办法包括接口的一致性,以及与内置类型的行为兼容. 3,"阻止误用"的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. 4,shared_ptr支持定制型删除器.这可以防范DLL问题,可以用来自动解除互斥锁. 条款19:设计class犹如设计type 博客地址:http://blog.csd

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

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

【Effective C++】设计与声明

条款18:让接口容易被正确使用,不易被误用 1,好的接口很容易被正确使用,不容易被误用.你应该在你的所有接口中努力达成这些性质. 2,“促进正使用”的办法包括接口的一致性,以及与内置类型的行为兼容. 3,“阻止误用”的办法包括建立新类型,限制类型上的操作,束缚对象值,以及消除客户的资源管理责任. 4,shared_ptr支持定制型删除器.这可以防范DLL问题,可以用来自动解除互斥锁. 条款19:设计class犹如设计type 如何设计你的类: 1,新type的对象应该如何创建和销毁? 影响到的设

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

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