effective c++ 笔记 (6)





//---------------------------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<Investment> createInvestment();

    

//  5:tr1::shared_ptr支持定制型删除器,可以防范DLL问题:

    std::tr1::shared_ptr<Investment> createInvestment()

    {

        std::tr1::shared_ptr<Investment> retval(static_cast<Investment*>(0),

                                                getRidOfInvestment);

        retval = ...;

        return retval;

    }

    

}


//#19   设计class犹如设计type

{

/*  1:type的对象应该如何被创建和销毁?

        这会影响到你的class的构造函数和析构函数,以及内存分配函数和释放函数。

        当然前提是如果你打算撰写他们。

    2:对象初始化和对象的赋值该有什么样的差别

        这个答案决定了构造函数和赋值操作符的行为,以及差异。

    3:type的对象如果被passed by value,意味着什么?

        copy构造函数用来定义一个typepass by value该如何实现。

    4:什么是新type合法值

        你的class必须维护自己的约束条件,也就决定了你的成员函数

        (特别是构造函数、赋值操作符、和所谓的setter函数)必须进行错误检查工作。

    5:你的新type需要配合某个继承图系吗?

        继承既有的class会受到哪些class的设计的束缚,特别是他们的函数是virtualnon_virtual的影响

        如果允许别人继承自己的类,就会影响你所声明的函数是否为virtual

    6:你的新type需要什么样的转换

        是否需要隐式转换、显式转换。

    7:什么样的操作符和函数对此新type而言是合理的?

        这个答案决定你为你的class声明哪些函数,其中某些该是member函数。

    8:什么样的标准函数应该驳回

        你不想要系统为你声明的函数要声明为private

    9:谁该取用新type的成员

        这个答案决定了哪个成员为publicprotectedprivate。以及哪个classfunction应该是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 Rantional operator* (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并没有封装性可言

 

}






郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。