四.设计与声明

本章将对良好C++接口的设计和声明作出阐述,对某些最频繁出现的错误提出警告,为class、function和template设计经常遇见的问题作出解答。

条款18.让接口容易被正确使用,不易被误用

请记住

1.好的接口很容易被正确使用,不容易被误用。
2.促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容。
3.阻止误用的办法包括建立新类型、限制类型的操作、束缚对象值、以及消除客户的资源管理责任。
4.tr1::shared_ptr支持定制删除器,这可防范DLL问题,可被用来自动解除互斥锁(见条款14)等等。

条款19.设计class犹如设计type

设计class是一项艰巨的工作,因为设计好的type很难。好的type有自然的语法,直观的语义,以及一个或多个高效实现品。那么如何设计一个高效的classes?
新type的对象如何被创建和销毁?这会影响到你的class的构造函数和析构函数以及内存分配和释放函数
对象的初始化和对象的赋值该有什么样的差别?这会决定你的构造函数和赋值操作符的行为,以及其间的差异,别混淆了“初始化”和“赋值”,这对应不同的函数调用。
新type的对象如果被passed by value,意味着什么?copy构造函数用来定义一个type的pass-by-value该如何实现。
什么是新type的“合法值”请妥善考虑你的数值定义集合。
新type需要维护配合某些继承关系吗?这会导致你是否受到另一个类的virtual或non-virtual函数的影响。
新type需要什么样的转换?请留心显示转换和隐式转换。
什么样的操作符和函数对新type是合理的这个问题的答案决定你会为你的class声明哪些函数。其中哪些该是成员函数。
什么样的标准应该驳回拒绝?这会让你思考决定另哪一个成员为public,哪个为protecetd,哪个为private,还会帮助你思考友元函数的定位和设计。
新type有多么一般化?追求极致一般化,应该考虑使用模板
真的需要一个新type吗?如非必要,勿增实体。
什么是新type的未声明接口?
请留意本节涵盖的class设计主题。

条款20.宁可以pass-by-reference替换pass-by-value.

1)

pass-by-value方式会带来高额的对象构造成本和析构成本。此外,还会在继承类中造成“切割”现象,当一个dericed class以pass-by-value方式传递并被视为一个base class对象时,其derived class的特质化性质会被切割掉。

2)

采用pass-by-reference方式应添加关键字const保证原有对象不被修改。

3)

在C++编译器的底层,reference往往以指针实现出来,因此pass-by-reference通常意味真正传递的是指针。

条款21.必须返回对象时,别妄想返回其reference.

1)

reference代表着一个对象的另一个名称,应该确保能找到这个对象的本名,才可使用reference.
请记住
绝不要返回pointer或reference指向一个local stack对象,或返回reference指向一个heap-allocated对象,或返回pointer和reference指向一个local static对象而有可能同时需要多个这样的对象。条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。

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

1)

如果对客户隐藏成员变量(即封装成员变量和方法),可以确保class的约束条件总是会得到维护,因为只有成员函数才可以影响它们。如果不隐藏,那么可以说不封装意味着不可改变,因为修改public的原始码是非常复杂的工作,是不可知的大工作量。protected也一样,可以认为protected不具备封装性质,它和public都是不可维护的。其实只有两种访问权限,private提供封装和不提供封装。
请记住

1.将成员变量声明为private,这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件得到保证,并可提供充分的实现弹性。
2.protected并不比public更具封装性。

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

越少代码能看到数据,则越多的数据可以被封装。

1)

将所有便利函数放在多个头文件内但隶属于同一个命名空间,意味客户可以轻松扩展这一组遍历函数,只需要添加更多的non-mmeber和non-friend函数到此命名空间中去。新函数就像其他旧有的遍历函数可用且整合为一体,这是class无法提供的另一个性质,因为class定义是对客户是不可扩展的。因为class不能分割为片段,必须整体定义。
请记住

宁可拿non-member non-friend函数替换member函数,这样做可增加封装性,包裹弹性和机能扩充性。

条款24.若所有参数都需类型转换,请采用non-member函数

只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。地位相当于“被调用之成员函数所隶属的那个对象”,如下:

class Rational{
public:
      Rational(int numerator = 0, int denominator = 1);
      int numerator() const;
      int denominator() const; //分子numerator和分母denominator
      const Rational operator* (const Rational& rhs) const;
private:
      ...
};
Rational oneHalf(1,2);
result = oneHalf * 2; //编译通过
result = 2 * oneHalf; //编译错误
//以函数形式重写:
result = oneHalf.operator*(2); //正确
result = 2.operator*(oneHalf); //错误

正确声明为:

class Rational{
      ...
};
const Rational operator*(const Rational& lhs, const Rational& rhs) //现在是一个non-member函数
{
      return Rational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator()); 
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2;
result = 2 * oneFourth; //通过编译

请记住

如果你需要为某个函数的所有参数进行类型转换,请使用non-member函数

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

swap的典型实现:

namespace std{
      template<typename T>
      void swap(T& a,T& b)
      {
            T temp(a); //类型T支持coping行为(copy构造函数和copy操作符)
            a = b;
            b = temp;
      }
}

这个典型对某些类型实现效率不高,比如以pimpl手法设计的class(pimpl是pointer to implemtation)WidgetImpl为例:

class WidgetImpl{
public:
      ...
private:
      int a, b, c;
      std::vector<double> v; //可能有很多数据,意味着复制时间很长
      ...
};
class Widget{  //pimpl手法
public:
      Widget(const Widget& rhs);
      Widget& operator=(const Widget& rhs) //复制Widget对象时,令它复制其WidgetImpl对象。
      {
            ...
            *pImpl = *(rhs.pImpl);
            ...
      }
      ...
private:
      WidgetImpl* pImpl;  //指针,所指对象含Widget数据
}

令Widget声明一个名为swap的public成员函数做真正的置换工作,然后将std::swap特化,令它调用成员函数:

class Widget{
public:
      ...
      void swap(Widget& other)
      {
            using std::swap;
            swap(pImpl, other.pImpl); //置换其pImpl指针
      }
      ...
};
namespace std{ //修订后的Widgets,调用其swap成员函数,与STL容器具有一致性。
      template<>
      void swap<widget>(Widget& a, Widget& b)
      {
            a.swap(b);
      }
}

接下来,再此基础上进行优化,让用户调用swap时取得较高效的template版本。首先声明一个non-member swap函数调用 member swap函数,将所有Widget的所有相关机能都被置于命名空间WidgetStuff内,整个结果看起来便是这样。

namespace WidgetStuff{
      ...
      template<typename T>
      class Widget{...};
      ...
      template<typename T>
      void swap(Widget<T>& a, Widget<T>& b)
      {
            a.swap(b);
      }
}

以上,已经讨论了default swap,member swap, non-member swap和std::swap特化版本以及对swap函数的调用。
请记住

1.当std::swap对某些类型效率不高时,提供一个swap成员函数,并保证该函数不抛出异常。
2.如果提供一个member swap,则也应该提供一个non-member swap用来调用前者,对于classes还应该特殊化std::swap;
3.调用swap时应针对std::swap使用using 声明式,然后直接调用swap
4.为“用户定义类型”进行std template全特化是好的,但不能够在std内加入新东西。

posted @ 2020-11-18 12:24  Viecgg  阅读(99)  评论(0)    收藏  举报