工作进度

2016/9/1 学习、工作、成长!

Posted by WangXiaoDong on September 1, 2016

时间:2016年9月1日 天气:晴:sunny:


Author:冬之晓:angry:
Email: 347916416@qq.com
MyAppearance: MyAppearance

    今天,陈博士过来找我了,问我最近的进度怎么样。然后还问我为什么电脑还是以前的,没有给我配置电脑。

首先总结一下今天的要点:

今天,就可以进入《Effective C++》的设计与声明的那一部分的学习了,非常期待!

条款18:让接口容易被正确使用,不容易被误用

程序员设计接口时应本的对用户负责的态度,“如果客户企图使用某个接口而却没有获得他所预期的行为,这个代码不应该通过编译;如果代码通过了编译,那它的作为就该是客户所想要的”。

假如设计的是一个日期类:

class Date
{
public:
    Date(int month, int day, int year);
    …
};

这个类看上去好像没有问题,提供的构造函数接口简单直观,然而它却做了一个假设——用户都能够按月、日、年的顺序来传参。

但事实上,一定会有不少用户记错这个顺序,比如我们常用的顺序是年、月、日,所以有同学会下意识地这样调用:

Date d(2013, 5, 28);

也许还有同学随便输入,导致传参不符合实际,比如:

Date d(5, 32, 2013);

其实当你设计的程序需要假定用户都能按你想像来进行操作的话,这个程序就存在隐患。

一种好的解决方法,是假定用户输入的数据都是不可靠的,需要对输入进行严格的检测,这是防御式编程的思想,这对那个心怀不轨的用户来说是很好的处理方法。但对于一般的用户,不合法的输入只是缺乏正确的引导。

如何去引导用户,如何让用户动最少的脑筋却能最佳地使用接口,是我们接下来要讨论的。

一种好的引导方式,是让用户在传参的时候,知道自己传的是什么,之前用是一个int,这显然不能提醒用户。那就创建相应的结构,像这样:

struct Month{
    Month(int mon):val(mon){}
    int val
};
struct Day{
    Month(int day):val(day){}
    int val
};
struct Year{
    Month(int year):val(year){}
    int val
};

接口就会设计成这样:

Data(Month , Day, Year);

那么调用的时候可能就需要这样去调用:

Data(Month(month), Day(day), Year(year));

虽然看起来可能会比较繁琐,但是用户犯错误的概率也大大降低了。 对于这些参数的取值范围,我们可以使用枚举类型。但是由于枚举类型可以被当做int来使用,所以并不是安全的。比较安全的办法是预先定义所有有效的month:

class Month
{
    explicit Month(int m):month(m) = delete;  //阻止生成新的月份
public:
	static Month Jan(){return Month(1);}   //函数,返回有效月份
	static Month Feb(){return Month(2);}
	...
    static Month Dec(){return Month(12);}
};

这样的话,你只能这样调用 class Date d1(Month::Jan(),Day(8),Year(2012)); 那么出错的机会就大大降低了。

预防用户不正确使用的另一个办法是,让编译器对不正确的行为予以阻止,常见的方法是加上const,比如初学者常常这样写:

if(a = b * c){…}

很明显,初学者想表达的意思是比较两者是否相等,但代码却变成了赋值。这时如果在运算符重载时用const作为返回值,像这样:

const Object operator* (const Object& a, const Object& b);

注意这里的返回值是const,这时编译器就会识别出赋值运算符的不恰当了。

“任何接口如果要求客户必须记得做某些事情,就有着不正使使用的倾向,因为客户可能会忘记做那件事”。所以像:

Investment* createInvestment();

就是不好的接口,容易造成资源泄露(对于像服务器一样长时运作的机器而言,资源泄露无疑是致命的)。

学习了智能指针之后,就可以这样做接口,让专门的资源管理类来掌握资源的回收:

auto_ptr<Investment> createInvestment();

shared_ptr<Investment> createInvestment();

使用shared_ptr会更好一些,因为它允许存在多个副本,不会在传递过程中改变原有资源管理者所持的资源,且支持自定义的删除器。书上说,自定义删除器可以有效解决”cross-DLL problem”,这个问题发生于在不同的DLL中出生(new)和删除(delete)的情况(对象生命周期横跨两个DLL,但在第二个DLL中结束生命的时候却希望调用的是第一个DLL的析构函数),自定义删除器则会在删除时仍然调用诞生时所在的那个DLL的析构函数。

当然,使用智能指针也是需要代价的,它比原始指针大(Boost库中实现的shared_ptr体积是原始指针的2倍)且慢,而且使用辅助动态内存。任何事物都具有两面性,权衡一下就会发现,智能指针能避免的资源泄露问题(好的接口),相较于它的空间和时间代价而言,都是值得的。

请记住:

  • 好的接口很容易被正确使用,不容易误用,应该在所有接口中努力达成这些性质。
  • 促进正确使用的办法包括接口的一致性,以及与内置类型的行为兼容;
  • 阻止误用的办法包括建立新类型、限制类型上的操作,束缚对象值,以及消除客户的资源管理责任;
  • tr1::shared_ptr支持定制型删除器,这可防范cross-DLL问题(这个问题发生于对象在动态链接程序库DLL中被new创建,却在另一个DLL内被delete销毁),可被用来自动解除互斥锁等等。Boost的shared_ptr是原始指针的两倍大,以动态分配内存作为簿记用途和“删除器之专属数据”,以virtual形式调用删除器,并在多线程程序修改引用次数时蒙受线程同步化的额外开销。(只要定义一个预处器符号就可以关闭多线程支持)。

设计模式(三)————单一职责原则

单一职责原则:就一个类来说,应该只有一个引起它变化的原因! 软件设计真正要做的许多内容,就是发现职责并把那些职责相互分离 判断是否分离:如果你能够想到多余一个的动机去改变一个类,那么这个类就具有多于一个的职责,就应该考虑职责分离。

今天这个原则使用的例子是俄罗斯方块,我试了试,结果搞了半天都没编出来,自己的水平还是不到家啊,今天的例子就先暂停,等我搞定了在放上去!!