设计与声明
条款18:让接口容易被正确使用,不易被误用
【Summary】
1. 好的接口很容易被正确使用,不容易被误用。你应该在你的所有接口中努力达成这些性质。
2. “促进正确使用”的办法包括接口的一致性,以及与内置类型的行为兼容。
3. “阻止误用”的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任。
4. tr1::shared_ptr支持定制删除器。这可防范DLL问题,可被用来自动解除互斥量等等。
class Month{
public:
static Month jan(){ return Month(1);}
static Month feb(){ return Month(2);}
/* *** */
static Month Dec(){ return Month(12);}
private:
explicit Month(int v){ // point1 : 构造函数私有化
var = v;
}
int var;
};
struct Year{
int var;
explicit Year(int v): var(v){} // point2 : explicit 避免隐式转型
};
struct Day{
int var;
explicit Day(int v): var(v){}
};
class Date{
public:
/* point3 */
explicit Date(const Month& m, const Day& d, const Year& y):
month_(m),day_(d),year_(y){}
private:
Month month_;
Year year_;
Day day_;
};
int main (){
Date d(Month::dec(), Day(12),Year(2016)); // point3, 限制初始化的格式,减少错误可能
return 0;
}
class Example{
public:
Example( ){}
~Example(){ printf("destructor.\n");}
};
/* bad
Example* getExample(){
return new Example();
}
*/
boost::shared_ptr<Example> getExample(){ // tr1::shared_ptr支持定制删除器
return boost::shared_ptr<Example>(new Example());
}
条款19:设计class犹如设计type
【设计规范】:
1. 新类型的对象应该如何被创建和销毁
全局变量、局部变量、static变量、动态分配
2. 对象的初始化和对象的赋值该有什么样的差别
赋值操作符、复制构造函数等
3. 新类型的对象如果被passed by value(值传递),意味着什么
4. 什么是新类型的“合法值”
成员变量的取值问题
5. 你的新类型需要配合某个继承图系吗
6. 你的新类型需要什么样的类型转换
7. 什么样的操作符和函数对此新类型而言是合理的
操作符重载
8. 什么样的标准函数应该驳回
声明为private的函数
9. 谁该取用新类型的成员
1.成员应该属于public、private还是protected 2.与friends类的耦合
10. 什么是新类型的“未声明接口”
暂略
11. 你的新类型有多少一般化
是否需要应该使用template
12. 你真的需要一个新类型吗
**
`
条款20: 宁以pass-by-reference-to-const 替换pass-by-value
1. 尽量以pass-by-reference-to-const替代pass-by-value。前者通常比较高效,并可避免切割问题。
2. 以上规则并不使用于内置类型,以及STL的迭代器和函数对象。对它们而言,pass-by-value往往比较适当
条款21: 必须返回对象时,别妄想返回其reference
1. 利用堆不行,利用static也不行
条款22: 将成员变量声明为private
1. 切记将成员变量声明为private
2. protected 并不比public更具封装性
条款23:宁以non-member、non-friend 替换member函数
1. 成员函数不仅可以访问private成员变量,也可以取用private函数、enum、typedef等等。
而非成员非友元函数能实现更大的
封装性
,因为它只能访问public函数。
2. 将所有便利函数(使用non-member non-friend的形式)放在多个头文件内但隶属同一个命名空间,
意味客户可以轻松扩展这一组便利函数
条款24:若所有参数皆需类型转换,请为此采用non-member函数
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。
条款25: 考虑写出一个不抛出异常的swap函数
1. 当std::swap对你的类型效率不高时,提供一个swap成员函数,并确定这个函数不抛出异常
2. 如果你提供一个member swap 【point 1】,也该提供一个non-member swap 【point 2】用来调用前者。
3. 对于class(而非templates),也请特化std::swap【point 3】。
4. 调用swap时应针对std::swap使用using声明式,然后调用swap并且不带任何“命名空间资格修饰”。【point 4】
5. 为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。【point 5】
namespace mynamespace{
template<typename T>
class Example{
public:
Example(const T& v):var(v){}
void swap(Example& e){ // point 1
using std::swap;
swap(this->var, e.var);
}
const T getVar()const {
return var;
}
private:
T var;
};
template<typename T> // point 2, swap不是重载,也不是特化
void swap(mynamespace::Example<T>& e1, mynamespace::Example<T>& e2){
e1.swap(e2);
}
template<>
void Example<char>::swap(Example<char>& e){
std::cout<<"special for char"<<std::endl;
using std::swap; // point 4
swap(this->var, e.var);
}
}
namespace std{ // point 3 & 5,函数模板std::swap特化,不能对std::swap重载
template<>
void swap<mynamespace::Example<char> >(
mynamespace::Example<char>& e1,
mynamespace::Example<char>& e2)
{
e1.swap(e2);
}
}
#include <iostream>
using namespace mynamespace;
template<typename T>
std::ostream& operator<<(std::ostream& os, Example<T>& e){
os<<e.getVar();
}
int main(){
Example<char> e1('1') , e2('2');
swap(e1,e2);
std::cout<<e1<<std::endl;
std::cout<<e2<<std::endl;
return 0;
}