//---------------------------15/04/06----------------------------
//#18 让接口容易被正确使用,不易被误用
{
// 1:为了防止客户输入错误的参数,可以使用外覆类型来区别:
struct Day
{
explicit Day(int d): val(d) {}
int val;
};
struct Month
{
explicit Month(int m): val(m) {}
int val;
};
struct Year
{
explicit Year(int y): val(y) {}
int val;
};
class Date
{
public:
Date(const Month& m,const Day& d, const Year& y);
...
};
// 这时,客户职能这么使用Date class:
Date d(Month(3), Day(30), Year(1995));
// 为了限制类型的值,比如一年只有12个月,Month应该反映这样的事实。
// 为了安全不直接使用enum,而是预先定义所有有效的Months:
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 m);
};
Date d(Month::Mar(), Day(30), Year(1995));
/* 2:预防客户错误的另一个办法是:限制类型内什么事可做,什么事不能做。也就是加上const。
3:除非有好理由,否则应该尽量令你的types的行为与内置types一致
4:任何接口如果要求客户必须记得做某些事情,就是有着“不正确使用”的倾向。
较佳接口的设计原则是先发制人,比如条款13中,
令factory函数返回一个智能指针,可以避免客户忘记使用智能指针: */
std::tr1::shared_ptr
// 5:tr1::shared_ptr支持定制型删除器,可以防范DLL问题:
std::tr1::shared_ptr
{
std::tr1::shared_ptr
getRidOfInvestment);
retval = ...;
return retval;
}
}
//#19 设计class犹如设计type
{
/* 1:新type的对象应该如何被创建和销毁?
这会影响到你的class的构造函数和析构函数,以及内存分配函数和释放函数。
当然前提是如果你打算撰写他们。
2:对象初始化和对象的赋值该有什么样的差别
这个答案决定了构造函数和赋值操作符的行为,以及差异。
3:新type的对象如果被passed by value,意味着什么?
copy构造函数用来定义一个type的pass by value该如何实现。
4:什么是新type的“合法值”
你的class必须维护自己的约束条件,也就决定了你的成员函数
(特别是构造函数、赋值操作符、和所谓的setter函数)必须进行错误检查工作。
5:你的新type需要配合某个继承图系吗?
继承既有的class会受到哪些class的设计的束缚,特别是他们的函数是virtual或non_virtual的影响
如果允许别人继承自己的类,就会影响你所声明的函数是否为virtual
6:你的新type需要什么样的转换
是否需要隐式转换、显式转换。
7:什么样的操作符和函数对此新type而言是合理的?
这个答案决定你为你的class声明哪些函数,其中某些该是member函数。
8:什么样的标准函数应该驳回
你不想要系统为你声明的函数要声明为private。
9:谁该取用新type的成员
这个答案决定了哪个成员为public,protected,private。以及哪个class或function应该是friend
10:什么是新type的“为声明接口”
它对效率、异常安全性以及资源运用提供何种保证
11:你的新type有多么一般化
看看自己是否应该定义一个新的class template
12:你真的需要一个新type吗
如果只是为既有的class添加机能,那么可能单纯定义一个或多个non member函数或templates
更能达到目标。
*/
}
//#20 宁以pass by reference to const替换pass by value
{
/* 1:pass by value 需要调用copy构造函数产出一个副本,这可能使得pass by value
成为昂贵的操作。如果传递的是一个自定义class,通常需要调用一次copy构造函数加 一次析构函数
如果这个class继承自别的类,又需要多调用好几次这两个函数。
2:by reference方式传递参数可以避免slicing(对象切割)问题:
一个derived class 对象以by value方式传递并被视为一个base class对象时,base class
的copy构造函数被调用,而“造成此对象的行为属于derived class对象”的部分被切掉了,只留
下了一个base class对象。这绝不是你想要的。
3:reference通常以指针来实现出来,所以pass by reference就相当于传递指针。因此如果是一些
内置类型对象(比如int) pass by value往往比 pass by reference的效率高些。
4:除了内置类型 和 stl的迭代器和函数对象,其他的对象都以pass by reference to const
替换 pass by value。
*/
}
//#21 必须返回对象时,别妄想返回其reference
{
// 1:一些值必须返回pass by value:
// 1>通过在stack上创建对象并返回这个对象的引用:
const Rantional& operator* (const Rantional& lhs,const Rantional& rhs)
{
Rantional result(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
/* 这里有两个点:
1)这样也调用构造了,效率并没提高。
2)返回了一个已经被销毁的对象。严重的错误!
2>通过在堆上创建对象并返回这个对象的引用:
*/
const Rantional& operator*(const Rantional& lhs,const Rantional& rhs)
{
Rantional result =new Rantional(lhs.n * rhs.n, lhs.d * rhs.d);
return result;
}
// 这样的话,谁负责delete?而且还是需要一次构造函数。
// 3>使用static对象:
const Rantional& operator* (const Rantional& lhs,const Rantional& rhs)
{
static Rantional result;
result = ...;
return result;
}
// 这样首先线程不安全,其次使用if((a * b) == (c * d))总是返回true;
// 2:当一个函数必须返回新对象时,就让那个函数返回一个新对象呗!
inline const Rantionaloperator* (const Rantional& lhs,const Rantional& rhs)
{
return Rantional(lhs.n * rhs.n, lhs.d * rhs.d);
}
}
//#22 将成员变量声明为private
{
/* 1:首先看看成员变量不该是public:
1>一致性:如果没有public成员变量,客户唯一能访问对象的办法就是使用成员函数
客户就不需要在访问成员时疑惑地试着记住是否使用小括号。
2>可以更加精准地控制成员变量:你可以通过函数控制成员变量的读写。
3>封装性:如果通过函数访问成员变量,日后就算更改了这个变量的计算方法,或者直接更改了变量,
客户也不知道,也不必知道。
不封装就意味着不改变。
2:成员变量不该是protected:
道理和public第三点一样,这样对derived class并没有封装性可言
}