18.让接口容易被正确使用
问题:
- 接口的参数
- 接口依赖其他调用
class Date {
public:
Date(int month,int day, int year;
}
以上代码问题:
- 容易以错误的顺序传递参数
- 可能传递无效的月份或天数
解决方法: - 通过导入新类型获得预防
struct Day {
explicit Day(int):val(d){}
int val;
};
class Date {
public:
Data(const Month& m, const Day& d, const Year& y);
};
Date d(Month(3), Day(30), Year(1995));
以上代码无法限制数据范围
解决方法:
- 利用enum表现月份
- 预定义有效的Month
class Month {
public:
static Month Jan() {return Month(1);}
static Month Feb() {return Month(2);}
private:
explicit Month(int m);
};
Date d(Month::Feb(), Day(30), Year(1995));
接口设计原则
- 接口不容易被误用
- 建立新类型
- 限制类型上的操作
- 消除客户的资源管理责任
- 接口一致性
- stl容器都有一个size接口
- 不要有的时length,有的是size
- 任何接口如果要求客户必须记得做某事,会有不正确使用倾向
// 会导致忘记删除指针
Investment* createInvestment();
// 解决方法,保存到智能指针
// 客户会忘记保存的到智能指针
// 解决方法:函数返回智能指针
auto_ptr<Investment> createInvestment()
19.设计class犹如设计type
需要考虑的问题:
- 新type对象该如何创建和销毁
- 构造函数,析构函数
- operator new,operator delete, operator new[] operator delete[]
- 对象初始化和对象赋值有什么区别
- 新type的对象如果被pass by value,意味着什么?
- copy构造函数如何实现
- 什么时新type的合法值
- 新type需要配合某个继承图系吗
- 如果继承某个既有类,受到该类的影响,特别时virtual,non-virtual的影响
- 如果容许其他类继承,影响你所声明的函数,尤其时析构函数是否为virtual
- 你的type需要什么样的转换
- T1需要转换为T2,T1内写类型转换函数operator T2,或着T2内写non-explicit 构造函数
- 转换是否需要为explicit的函数
- 什么样的操作符和函数是合理的
- 该谁取用新的type的成员
- public,protected,private,friend
- 你的新type有多么一般化
- 是否需要为class template
20.宁以pass-by-reference-to-const替代pass-by-value
使用pass-by-value问题
- 传递的类对象执行copy构造函数,造成性能低下
- 类对象内部的成员变量执行copy构造函数,造成性能低下
解决方法:
- 传递常量引用
- 传递引用可以避免切割发生(切割后virtual函数调用的是基类的virtual函数)
- 对于内置对象,pass-by-value更高效
- 编译器会把内置对象放入缓存器
- 小型的类对象,pass-by-reference更高效
- 编译器可能不会对小型类对象优化
- 类对象大小随着需求增加可能会变化,会变为大的类对象
21.必须返回对象时,别妄想返回其reference
返回的reference在内存模型中存在两种情况,stack空间和heap空间
栈空间
代码实现如下:
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational result();
return result;
}
函数返回后,栈空间的result将会被销毁,使用被销毁的引用会引入未定义的行为。
堆空间
代码实现如下:
const Rational& operator*(const Rational& lhs, const Rational& rhs)
{
Rational* result = new Rational();
return *result;
}
Rational w,x,y,z;
w = x*y*z;
以上代码执行乘法操作时会造成资源泄露。
综上,无法返回对象的引用,返回对象进行数据拷贝造成的性能问题编译器会进行优化。
23.宁以non-member,non-friend替换member函数
将便利函数以non-member,non-friend方式实现
24.若所有参数类型皆需要转换,请为此采用non-member函数
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
const Rational operator* (const Rational& rhs) const
};
如果使用成员函数形式,则以下代码无法通过编译。
Rational result, rational;
result = 2 * rational;
如果采用non-member函数形式则可以
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
};
const Rational operator*(const Rational& lhs, const Rational& rhs);
Rational result, rational;
result = 2 * rational;
2先通过隐式转换为Rational后调用operator*