条例18~23(设计与声明)

目录

条例18

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

总结

条例19

设计class犹如设计type

条例20

宁以const的引用传参替代传值传参

总结

条例21

必须返回对象时,别妄想返回他的引用

总结

条例22

将成员变量声明为private

总结

条例23

宁以非友元非成员函数替换成员函数

总结


条例18

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

  • 我们不能总是指望客户正确的使用接口。接口本身的设计就应该阻止用户非法的使用接口。
  • 我们可以通过让参数变成指定类型,来防止类型错误的问题。给所有参数都提供一个独有的类,让客户传参的时候通过对应类型的构造函数造出对应的对象。这样当客户试图用不同类型的参数传参的时候,编译器会发现并报错。
  • 限制客户另一个错误的办法是限制类型内什么是可以做,什么是不能做。例如适当的加const.
  • 同时不能过度依赖让客户做某些事,比如delete。因为客户可能不会释放或者释放两次。可以让返回的资源直接寄托给智能指针,不给客户操作的机会。同时为了防止客户对我们返回的值进行错误的析构,我们可以直接在share_ptr内绑定上指定的删除器。由于share_ptr的构造函数固定需要传指针,有的时候我们可能没有拿到原生指针,可以先用static_cast构造出智能指针,在获得了对象后再交给智能指针管理。若能直接获得对象就可以直接交给智能指针效率会更高一些。
  • 使用share_ptr的另一个优点是会自动使用它的每个指针专属的删除器。这样就能消除交叉动态链接问题。这个问题是一个dll new 了一个对象却在另一个dll内被释放。但share智能指针的删除器会追踪会原来的dll.

总结

  • 好的接口很容易被正常使用,不容易被误用。应该在所有接口努力达成这些性质
  • 促进正确使用的办法包括接口的一致性,以及与内置行为兼容
  • 阻止误用的办法包括建立新类型,限制操作,消除客户的资源管理责任,束缚对象值
  • 使用share_ptr的定制删除器用来防范dll问题,还可以用来自动解除互斥锁。

条例19

设计class犹如设计type

  • class设计就是type的设计。在定义一个新的type之前,要考虑之前条例的所有细节

条例20

宁以const的引用传参替代传值传参

  • 通常情况下传值拷贝的效率是十分低下的,尤其在继承之后的类传值拷贝的时候,需要拷贝其父类,用引用会很大的提高效率。通过引用可以规避调用构造函数和析构函数。同时在使用引用的时候最好声明为const,否则会产生引用的对象是否会被更改的忧虑。
  • 同时使用引用传参还能避免对象切割问题。当一个子类以传值拷贝的方式传递并且被视为父类的时候,就会把它当作父类而不是子类,从而调用父类的函数,这就导致了切片问题。这个问题可以通过传引用const来解决。
  • 引用的底层往往通过指针来实现,若有个对象是内置类型,则可以考虑传值拷贝。以情况而定,若对象小其内含的东西大则复制拷贝十分昂贵。
  • 可以假设适合传值拷贝的对象是内置类型,和stl的迭代器和函数对象

总结

  • 尽量以const的引用替换传值拷贝,前者比较高效,并可避免切割问题。
  • 以上规则不适用于内置类型,以及stl的迭代器和函数对象。对这些而言使用传值拷贝更加恰当。

条例21

必须返回对象时,别妄想返回他的引用

  • 并不是所有函数的返回值都适合返回引用。因为引用是起别名,有的时候可能根没有可以引用返回的对象,这时若坚持使用引用就需要主动创建对象.有两种方式创建对象,在堆上或者在栈上。这又导致了新的问题,你需要调用构造函数和析构函数。并且有可能会导致返回一个已经被销毁的对象。若用static解决这个问题又会面临线程安全问题。

总结

  • 绝不能返回一个指向栈上对象的指针或者引用,或者返回一个指向堆上对象的引用,或者返回一个局部静态变量。
  • 该使用传值拷贝的时候不要坚持使用引用返回

条例22

将成员变量声明为private

  • 若成员变量不是public,则外来客户只能通过接口访问类内成员,这样能保持接口一致性。同时通过接口获取成员变量,可以在接口内对用户加上限制条件。
  • 同时使用接口意味这封装了一层,用户将对接口内的实现不知情,就算更改了内部实现,也不会影响客户的使用。将成员变量隐藏在函数接口的背后,可以为所有可能的实现提供弹性。
  • 封装很重要,而使用public意味着没有封装,意味着客户代码高度依赖类的底层成员们难以更改。使用protected并不会有效解决这个问题。即使使用保护的成员变量,在派生类的眼里和共有没有什么区别。保护成员的改变会导致派生类的改变。
  • 从封装的角度来看,只有两种权限,私有(封装)和其他(不封装)

总结

  • 将成员变量生命为private。这可赋予客户访问数据的一致性,并细化访问权限和约束条件,并让类有充分的的弹性。
  • 保护并不比公有更具有封装性。

条例23

宁以非友元非成员函数替换成员函数

  • 虽然面向对象建议数据应该静可能被封装,但有的时候同样的函数在类外实现的封装性可能会好于实现成类内成员函数。
  • 若你实现成类内成员函数,它还能访问类内的其他成员和函数。若你实现成类外的函数则不会增加访问的权限。要注意的两个点是,这个结论只是用于非友元,友元函数的访问大小和类内函数是一致的。另一个点是虽然他不是当前类的友元,但是不代表他不能成为另一个类的友元。
  • 比较自然的做法是可以写成类外函数并跟类放在同一命名空间内。同时这样也能分离不同的功能,放在不同的头文件内,但是用同一个命名空间。用户用哪个功能就包含哪个头文件即可,这样能减少编译的代码量。std官方库的组织形式就是这种。这种写法意味着用户可以在不破坏封装性的情况下拓展相关函数。只需要在对应的命名空间下新建一个头文件即可。这点是class做不到的,哪怕是用继承,因为子类不能访问父类内的私有成员。

总结

  • 宁可用非友元函数替换类内成员函数。这样做能增加封装性,弹性,和扩展性。

你可能感兴趣的:(Effective,c++,c++)