第18条 使接口易用,不易被误用
- 设计的接口应清晰,不易被误用
- 可以通过接口的一致性、内置类型等促进接口的正确使用
- 可以通过建立新类型、限制类型的操作、束缚对象值等消除用户的资源管理责任
- 对于指针等资源,善用智能指针
通过限制类型来提高接口的易用性:
// 原型
class Date{
Date(int year, int month, int day);
};
问题 : 容易传错参数顺序
使用内建类型
class Date{
Date(Year year, Month month, Day day);
};
struct Year{
explicit Year(int y) : _y(y) {...}
int _y;
};
智能指针解决手动释放、特化delete、cross-DLL problem
- 当某资源需要特化其释放时,可以使用智能指针的自定义释放函数
auto deleter = [](A *) {};
unique_ptr<A, decltype(deleter)> p1(new A, deleter);
shared_ptr<A> p2(new A, deleter);
- cross-DLL problem
这个问题发生于“对象在动态连接程序库(DLL)中被new创建,却在另一个 DLL 内被 delete销毁”。在许多平台上,这一类“ DLL之 new/delete 成对运用”会导致运行期错误。trl::shared ptr没有这个问题,因为它缺省的删除器是来自“trl::shared ptr诞生所在的那个DLL”的 delete
第19条 设计class如设计新type
第20条 以pass-by-reference-to-const 替换 pass-by-value
-
尽量以 pass-byreference-to-const 替换 pass-by-value。前者通常比较高效,并可避免切割问题(slicing problem)
-
以上规则并不适用于内置类型,以及 STL 的迭代器和函数对象。对它们而言pass-by-value往往比较适当。
pass-by-value的性能开销更大
class H {
public:
H() {}
H(const H &) { cout << "Call copy construct " << endl; }
H &operator=(const H &) { cout << "Call copy " << endl; }
~H() { cout << "Delete H " << endl; }
};
void h1(H) {
}
void h2(const H &) {
}
int main() {
H h;
h1(h);
cout << "----" << endl;
h2(h);
cout << "----" << endl;
}
Call copy construct
Delete H
----
----
Delete H
函数pass-by-value调用一次,将承担拷贝构造的消耗,并在结束时承担析构消耗.
pass-by-reference 可避免切割问题
在多态中,常有子类继承基类. 接口函数多声明为基类类型,通过引用调用可根据其实际类型调用对应函数,而pass-by-value则会将派生类切割为基类(其表现为基类).
class Base {
public:
virtual void show() const {
cout << "Call Base" << endl;
}
};
class Derived : public Base {
public:
virtual void show() const override {
cout << "Call Derived" << endl;
}
};
void show1(const Base &base) {
base.show();
}
void show2(Base base) {
base.show();
}
int main() {
Derived derived;
show1(derived);
show2(derived);
}
Call Derived
Call Base
第21条 必须返回对象时,不要返回其reference
- 绝对不要返回:
- ①local stack对象的指针或引用
- ②heap-allocated对象的引用
- ③可能需要多个同样对象的local static对象的指针或引用
常见误用场景
- 返回local stack对象
A& get(int i){
A result(i);
return result;
}
这里返回的是局部栈上对象,在函数结束时就被析构.
- 返回了heap-allocated
H &get(int i) {
auto p = make_shared<H>(i);
return *p;
}
H &get_2(int i) {
H *p = new H(i);
return *p;
}
int main() {
auto &obj1 = get(1);
auto &obj2 = get_2(2);
}
// 输出
Delete H 1
问题是谁对result进行delete?
第22条 将成员声明为private
- 切记将成员变量声明为 private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,并提供 class 作者以充分的实现弹性。
- protected 并不比 public 更具封装性
第23条 宁可以non-friend, non-member替代member函数
- 以non-friend, non-member替代member函数可以增加封装性、包裹弹性和技能扩充性
如果要你在一个member 函数(它不只可以访问class内的 private数据,也可以取用 private 函数、enums、typedefs 等等)和一个non-membernon-friend 函数(它无法访问上述任何东西)之间做抉择,而且两者提供相同机能,那么,导致较大封装性的是non-member non-fiiend函数,因为它并不增加“能够访问class内之private成分”的函数数量。例如:
class WebBrowser{
public:
void clearA();
void clearB();
void clearC();
...
void clearAll_1(){
clearA();
clearB();
clearC();
}
};
// 有限选择non-member函数
void clearAll_2(const WebBrowser& wb){
wb.clearA();
wb.clearB();
wb.clearC();
}
将所有便利函数放在多个头文件内但隶属同一个命名空间,意味客户可以轻松护展这一组便利函数。
第24条 若所有参数皆需要类型转换,则采用non-member函数
例如在隐式转换中
class Rational {
public:
Rational(double _i = 0) : i(_i) {}
double i;
const Rational operator*(double j) { return Rational(i * j); }
};
const Rational operator*(const Rational &a,const Rational &b) {
return Rational(a.i * b.i);
}
member函数不支持 例如 2*a
这样的操作。
第25条 写出不抛出异常的swap函数
原文的内容在C++11中可通过右值这个概念实现
template<typename T>
void Swap(T &a, T &b) {
T&& temp = std::move(a);
a = std::move(b);
b = std::move(temp);
}