现在的位置: 首页 > 综合 > 正文

Effective C++(四)设计与声明

2013年07月25日 ⁄ 综合 ⁄ 共 3452字 ⁄ 字号 评论关闭
四、设计与声明

条款18:让接口容易被正确使用,不易被误用
1、引入新类型
Date(const Month& m, const Day& d, const Year& y);
导入新类型可以有效预防接口被误用
以函数替换对象,表现某个特定月份
限制类型内可以定义的操作。常见的限制是加上const
2、令type的行为与内置types一致
提供行为一致的接口。例如STL容器的接口十分一致
3、忘记使用智能指针
较佳接口设计原则是先发制人,令函数返回一个智能指针
4、请记住
1)好的接口容易被正确使用,不容易被误用
2)"促进正确使用"的办法包括接口的一致性,以及与内置类型的行为兼容
3)"阻止误用"的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任
4)tr1:shared_ptr支持定制型删除器,可防范DLL问题,可别用来自动解除互斥锁
条款19:设计class犹如设计type
1、class和type
定义class,也就是定义一个新type,包括重载函数和操作符、控制内存的分配和归还、定义对象的初始化和终结
2、设计规范
1)对象的创建和销毁
class的构造函数和析构函数,以及内存分配函数和释放函数(operator new,operator new[], operator delete和operator delete[])的设计
2)对象的初始化和赋值
注意初始化和赋值的区别,对应不同的函数调用
3)对象值传递
copy构造函数
4)类的"合法值"
决定了必须维护的约束条件,决定了你的成员函数必须进行的错误检查工作
5)是否配合继承
当其它类继承时,考虑函数特别是析构函数是否为virtual
6)是够转换
如果只允许explicit构造函数存在,定义转换函数
7)定义的操作符和函数
决定声明哪些member函数
8)驳回的标准函数
必须声明为private
9)可以使用该class的成员
决定成员的public、protected和private,决定class或function是否为friend
10)未声明接口
对效率、异常安全性以及资源运用提供哪种保障
11)一般化程度
考虑是否定义一个新的class template
12)是否真的需要
如果只是定义新的derived class以便为既有的class添加机能,那么说不定淡出定义一或多个non-member函数或模板,更能达到目标
3、请记住
class的设计就是type的设计
条款20:宁可pass-by-reference-to-const替换pass-by-value
1、by value方式函数
bool validateStudent(Student s);    //by value
函数参数是实际实参的副本,由copy构造函数产出
by value会多次调用copy构造函数、构造函数和析构函数
2、pass-by-reference-to-const
bool validateStudent(const Student& s);
1)效率更高,因为没有任何构造函数或析构函数被调用
const很重要:by value方式函数内只能够修改该实参的副本,声明为const避免修改传入的实参
2)避免对象切割
当一个derived class对象以by value方式传递被视为一个base class对象,base class的copy构造函数被调用,发生切割!
reference往往以指针实现,pass by reference意味着真正传递的是指针
3、请记住
1)尽量以pass-by-reference-to-const替换pass-by-value。前者通常比较高效,并可以避免切割问题
2)以上规则不适用于内置类型,以及STL的迭代器和函数对象。对于它们,pass-by-value更适合
条款21:必须返回对象时,别妄想返回其reference
1、reference
reference声明式是某物的另一个名称
如果返回一个reference,后者一定指向某个既有的对象,内含已有的结果,显然是不合理的!
2、函数创建对象的途径
1)stack空间
Rational result(lhs.n*rhs.n, lhs.d*rhs.d);
return result;
local对象在函数退出前被销毁,无定义行为!
2)heap空间
Rational* result=new Rational(lhs.n*rhs.n, lhs.d*rhs.d);
return result;
需要有对象对new出来的对象实施delete,没法获取!
3)static对象
static Rational result;
result=...;
return result;
①多线程安全
②两处返回引用时,调用得到的永远是static对象的最新值
3)正确的做法
让函数返回一个新对象
inline const Rational operator*(const Rational& lhs, const Rational& rhs)
{
    return  Rational(lhs.n*rhs.n, lhs.d*rhs.d);
}
4、请记住
1)不要返回pointer或reference指向一个local stack对象
2)不要返回reference指向一个heap-allocated对象
3)返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象
条款22:将成员变量声明为private
1、private成员变量
1)不需要迷惑记住是否该使用小括号,区别函数和变量
2)通过函数对成员变量的访问有更精确的控制
3)封装。内部函数变换,对class用户不影响,不用修改太多代码
2、请记住
1)切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允许约束条件获得保证,并提供class作者以充分的实现弹性
2)protected并不比public更具封装性
条款23:宁以non-member、non-friend替换member函数
1、non-member non-friend函数
提供同样机能情况下,不增加能够访问class内的private成分
愈多函数可访问,数据的封装性就愈低
注:可以是另一个class的member函数
2、比较自然的做法
让non-member函数位于同一个namespace内
namespace可以跨越多个源码文件,而class不同
把所有函数放在多个头文件内但隶属同一个命名空间,降低编译依赖,方便扩展
3、请记住
宁可用non-member non-friend函数替换member函数,增加封装性、包裹弹性和机能扩充性
条款24:若所有参数皆需类型转换,请为此采用non-member函数
1、explicit
Rational(int numerator=0, int denominator=1);   
构造函数不为explicit,允许隐式转换
result=oneHalf*2;   //很好  result=oneHalf.operator*(2);
result=2*oneHalf;   //错误!result=2.operator*(oneHalf);
2并没有相应的class,没有operator*成员函数
2、隐式类型转换
只有参数被列于参数列,而不是被调用成员函数隶属的那个对象
3、non-member函数
允许编译器在每一个实参身上执行隐式类型转换
尽量避免friend函数
4、请记住
如果需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么函数必须是non-member
条款25:考虑写出一个不抛异常的swap函数
1、缺省swap(置换)
template<typename T>
void swap(T&a, T&b)
{
    T temp(a);
    a=b;
    b=temp;
}
类型T支持copy构造函数和copy assignment操作符
2、请记住
1)当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定它不抛出异常
2)如果提供一个member swap,也该提供一个non-member swap来调用前者。对于class(而非template),请特化std::swap
3)调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何"命名空间资格修饰"
4)为"用户定义类型"进行std template全特化是好的,当不要尝试在std内加入某些对std而言全新的东西

抱歉!评论已关闭.