Effective C++之四:设计与声明

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

(1)“阻止误用”的办法包括建立新类型限制类型上的操作束缚对象值,以及消除客户的资源管理责任。

客户可能输入错误的日子

class Date{

pubilc:

    Date(int month,int day, int year);

    ...

};


Date(30,3,1995);

可以通过导入新的类型进行预防:

class Date{

pubilc:

    Date(int month,int day, int year);

    ...

};


struct Day{

    explicit Day(int d):val(d){}

    int val;

};


class Month{                //一年只有12个月份,不能出现无效月份

public:

    staticMonth Jan() {return Month(1)};  //返回有效月份

    staticMonth Feb() {return Month(2)};  //以函数替换对象,表现某个特定月份。non-local-static的使用!

    ...

    static Month Dec() {return Month(12)};

    ...

private:

    explicit Month(int m);                 //阻止生成新的月份

    ...                                     //这是月份专属数据

};


struct Year{

    explicit Year(int y):val(y){}

    int val;

};


class Date{

pubilc:

    Date(const Month& m,const Day& d, const Year& y);

    ...

};


int main()

{

    Date d(30,3, 1995); //错误

    Date d(Day(30), Month(3), Year(1995));    //错误

    Date d(Month(3), Day(30), Year(1995));    //错误

    Date d(Month::Mar(), Day(30), Year(1995));//正确。

    return0;

}


(2)

sharp_ptr支持定制型删除器。这可防范DLL(动态链接库)问题,可被用来自动解除互斥锁(条款14)等。

DLL问题是:对象在动态链接库中被new创建,却在另一个DLL被delete,这会导致运行期错误。

shared_ptr没有这问题,因为它缺省的删除器是来自它诞生的那个DLL的delete。

shared_prt<Investment> createInvestment()

{

    shared_prt<Investment> retVal(static_cast<Investment*>(0), //建立一个空指针。需要static_cast转换。直接0编译不过。

                                  getRidOfInvestment);          //shared_prt设立一个删除器

    retVal = ...;

    return retVal;

}


条款19 设计class犹如设计type


class设计就是type的设计。


(1)新type的对象应该如何被创建和销毁(条款49-52)

(2)对象的初始化和对象的赋值该有什么样的差别(条款4)

(3)

新type的对象如果被passed by value意味着什么? 会调用构造函数

记住copy构造函数用来定义一个type的pass-by-value该如何实现?如果有继承,还要先调用基类的构造函数和自己成员变量的构造函数

(4)什么是新type的合法值?(条款18)

(5)新的type需要配合某个继承图系么?基类是virtual与否(条款34、36);是否允许其他类继承你,会影响你的析构函数是不是virtual(条款7)

(6)你的新type需要什么样的转换?(条款15有隐式显式转换的范例)

(7)什么样的操作符和函数对此type而言是合理的。这个问题答案决定将声明哪些函数,其中哪些是member函数(条款23/24/46)

(8)什么样的标准函数应该驳回? 那就把这些声明为private (条款6)

(9)谁该取用新type的成员?哪些是public、private、friends

(10)什么是新type的未声明接口?(条款29)

(11)你的新type有多么一般化?

(12)你真的需要一个新type么?还是说定义一个派生类就可以。


条款20 宁以pass-by-reference-const替换pass-by-value


尽量以pass-by-reference-const替换pass-by-value,前者比较高效并且可以解决切割问题

(1)高效

bool vaildstudent(student s);//这个方式被淘汰!

student plato;

bool platoisok = vaildstudent(plato);  //会构造一个新的对象s,然后函数完毕再销毁。其中student里面的基类和string成员变量的构造函数和析构函数都要被调用。浪费资源。


bool vaildstudent(const student& s);   //const避免传入的对象被改变。


(2)解决切割问题

void printNameAndDisplay(Window w) //会发生切割。

{//如果传递派生类,会被认为是基类对象,只会调用基类的构造函数。丧失了多态的特性。

    cout<<w.name();

    w.display();

}


void printNameAndDisplay(const Window& w)  //如果传进来是Window的派生类,就不会发生切割。

{                                           //因为引用是用指针实现出来的

    cout<<w.name();

    w.display();

}


(3)

但是以上的规则并不适用于内置类型,以及STL的迭代器和函数对象。对他们而言,pass-by-value比较合适。


条款21 必须返回对象时,别妄想返回reference


绝不要返回pointer或reference指向一个local stack对象(会被销毁),或返回reference指向一个heap-allocated对象(可能忘记手动delete),或返回pointer或reference指向一个local static对象而有可能同时需要多个这样的对象

(1)

//有理数相乘的函数

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs)

{

    Rational result(lhs.n * rhs.n, lhs.d * rhs.d);

    return result;                                     //错!返回的引用指向的东西在函数完毕后就销毁了

}

(2)

//有理数相乘的函数

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs)

{

    Rational *result = new Rational(lhs.n * rhs.n, lhs.d * rhs.d);

    return result;                                     //更错!谁来delete??肯定会内存泄露

}

(3)

//有理数相乘的函数

const Rational& operator*(const Rational& lhs,

                          const Rational& rhs)

{

    static Rational result;        //错错错!每一个返回的引用都是指向同一个内部定义的static Rational对象

    result = ...;

    return result;

}

(4)

//有理数相乘的函数

inline const Rationaloperator*(const Rational& lhs,

                                const Rational& rhs)

{

    return Rational(lhs.n * rhs.n, lhs.d * rhs.d);     //正确

}


(5)单例模式

条款4已经为“在单线程环境中合理返回reference指向一个local static对象”提供了一份设计实例。


条款22 将成员变量声明为private

(1)

切记将成员变量声明为private。这可赋予客户访问数据的一致性、可细微划分访问控制、允诺约束条件获得保证,方便函数内部实现变化。并提供class作者以充分的实现弹性。


访问数据的一致性:所有访问成员变量都需要成员函数来访问,那就一致了。

class AccessLevels{  //可细微划分访问控制

public:

    int getReadOnly()const     {returnreadonly};

    void setReadWrite(int value) {readwrite = value;}

    int getReadWrite()const    {returnreadwrite};

    void setWriteOnly(int value) {writeonly = value;}

private:

    int noAccess;  //禁用

    int readonly;  //只读

    int readwrite; //只写

    int writeonly; //读写

};


(2)

protected 并不比 public更具封装性。


条款23 宁以non-menber、non-friend替换 member函数


(1)

宁以non-menber、non-friend替换 member函数。  这样做可以增加封装性、包裹弹性和机能扩充性

class WebBrowser{

public:

    void func1();

    void func2();

    void func3();

    ...

    //方式一:使用成员函数

    void doeverything();//里面调用 func1func2func3

};

//方式二:使用non-membernon-friend函数。我们选择使用这一种方法!

void outsizefunc(WebBrowser& wb)

{

    wb.func1();

    wb.func2();

    wb.func3();

}


1.增加代码的封装性:有多少函数可以访问变量。

2.提高代码的扩展性

3.降低代码的编译依存性。


(2)

可以把不同功能的便利函数放在不同的头文件里面,但是放在同一个namespace(命名空间)里面就可。

使用的时候只需要#include相对应功能的头文件就可以。允许客户只对他们所用的部分系统形成编译相依。

要想实现这种切割机能的方式,就不适用于class成员函数,因为一个class必须整体定义,不能被分割为片段。

//头文件 "webbrowser.h",针对class本身及WebBrowser核心机能

namespace WebBrowserStuff

{

class WebBrowser{...};

    

    ... //核心机能。几乎所有客户都需要的

        //non-member函数

}


//头文件 "webbrowserbookmarks.h"

namespace WebBrowserStuff//将所有便利函数放在多个头文件内但隶属于同一个命名空间,意味着客户可以轻松扩展这一组遍历函数。

{ //他们需要做的就是直接添加更多的non-member、non-friend函数到此命名空间中

    ... //与书签相关的便利函数

}



//头文件 "webbrowsercookies.h"

namespace WebBrowserStuff

{

    ... //cookie相关的便利函数

}


条款24  所有参数皆需类型转换,请为此采用non-member函数

(1)

如果需要为某个函数的所有参数(包括this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是non-member

class Rational{

public:

    Rational(int numerator =0, int denominator =1);   //没有explicit,支持隐式转换

    int numerator()const;

    int denominator()const;

private:

    ...

};


//必须写为non-member函数,不然 ret = 2 * onefour;就会错。因为只有当 参数 被列进参数列,这个参数才是隐式转换的合格参与者

const Rationaloperator*(constRational& lhs,

                         constRational& rhs)

{

    returnRational(lhs.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());

}


int main()

{

    Rational onefour(1,4);

    Rational ret;

    ret = onefour * 2//可以,2被隐式转换了

    ret = 2 * onefour; //可以,2被隐式转换了

    return0;

}


(2)

无论何时,如果你可以避免friend函数就该避免。



条款25 考虑写出一个不抛异常的swap函数

(0)

如果swap的缺省代码对你的class或class template提供可接受的效率,你不需要额外做任何事。


(1)正在编写class的 swap

当std::swap对你的效率不高时,做下面几件事:

1、提供一个public swap成员函数,并确定这个成员函数不抛出异常。

2、在你的class或template所在的命名空间内提供一个non-member swap,并令它调用上述的swap成员函数。

3、如果你正在编写一个class(而非class template),为你的class特化std::swap。并令她调用你的swap函数。

4、最后,如果你调用swap,请确定包含一个using声明式,以便让std::swap在你的函数内曝光可见,然后不加任何namespace修饰符,赤裸裸地调用swap。


class Widget

{

public:

    Widget(const Widget&);

    Widgetoperator=(const Widget& rhs)

    {   ...

        *pImpl = *(rhs.pImpl);

        ...

    }

    void swap(Widget& rhs)      //public swap成员函数

    {

        using std::swap;        //包含using声明式

        swap(pImpl, rhs.pImpl); //赤裸裸调用

    }

private:

    WidgetImpl *pImpl;

};


//如果你正在编写一个class(而非class template),为你的class特化std::swap。并令她调用你的swap函数。

namespace std

{

    template <>

    void swap<Widget>(Widget& a, Widget& b)   //修订后的std::swap特化版本

    {

       a.swap(b);

    }

}


(2)如果我们的类是一个模板类,但是c++并不允许对函数偏特化,所以我们在类的命名空间中重载swap函数就行,这是个基本套路!

namespace WidgetStuff

{

    template<typename T>

    class Widget

    {

    public:

        Widget(const Widget&);

        Widgetoperator=(const Widget& rhs)

        {   ...

            *pImpl = *(rhs.pImpl);

            ...

        }

        void swap(Widget& rhs)      //public swap成员函数

        {

            using std::swap;        //包含using声明式

            swap(pImpl, rhs.pImpl); //赤裸裸调用

        }

    private:

        WidgetImpl *pImpl;

    };


    

    template<typename T>

    void swap(Widget<T> &lhs, Widget<T> &rhs)   //non-member swap函数。这里并不属于std命名空间

    {

        lhs.swap(rhs);

    }

}



//调用swap时应该针对std::swap使用using声明式,然后调用swap并且不带任何命名空间资格修饰

template <typename T>

void doSomthing(T& lhs, T& rhs)

{

    using std::swap;     //std::swap在此函数内可用

    swap(lhs, rhs);      //T类型对象调用最佳swap版本

}


(3)

为“用户定义类型”进行std templates全特化是好的,但千万不要尝试在std内加入某些对std而言全新的东西。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值