四.设计与声明
本章将对良好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内加入新东西。

浙公网安备 33010602011771号