Effetive C++读书笔记-第4章

4 设计与声明

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

理想上,如果用户企图使用一个接口而却没有获得预期的行为,代码就应该不通过编译,例如一个接口要求输入一个月份,此时输入小于1和大于12的数都是无效的,我们应该尽量使用不兼容这些数的接口,即输入小于1或者大于12的数就无法通过编译。

可选的一个方法是使用enum,但是enum不具备类型安全性,也就是说可以被转换成其他的类型使用(如int)。或者使用类,预先定义所有有效的月份。

class Month{
    static Month Jan(){ return Month(1); }
    static Month Feb(){ return Month(2); }
    ...
    static Month Dec(){ return Month(12); }
    ...
private:
    ecplict Month(int m);
    ...
}; 
DateaAPI(Month::Mar(), ... );   //可以杜绝不正确的输入

再或者对于工厂函数返回原始指针,但是用户可能会忘记将其赋予给智能指针从而导致内存泄漏,于是我们可以直接让工厂函数返回智能指针,并在函数内部直接指定删除器(deleter)。即:

shared_ptr<class_type> create();    //返回指向class_type的智能指针

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

1 通过const引用传递可以避免多余的构造和析构过程,传值会因为临时对象而产生多余的构造和析构过程。

通过const引用传递可以实现多态,C++的多态只能通过引用(引用的底层也是通过指针实现的)和指针来实现,那么如果接口是父类指针的话,按值传递子类对象,将会造成拷贝构造函数仅仅构造父类部分,子类部分不予以拷贝的情况(不支持多态),只有通过const引用传递子类对象,才能正确的调用子类重写的父类虚函数。

2 对于内置类型(int、double等),以及STL的迭代器和函数对象等,最好使用pass-by-value。

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

对于函数内部的局部变量是不可以返回其引用的,同样也不可以对内部的指针返回引用,因为会产生内存泄漏(假设T *f(args),那么f(f(args))会产生内存泄漏并且无法进行delete)。如果使用static,那可能数据会被改变,无论是单线程还是多线程都是数据不安全的。

22 将成员变量声明为private

将数据设置为private可以提供较好的封装性,但是public和protected都不能提供太好的封装性,public的成员变量可以被外部访问,而protected的成员变量可以被派生类自由访问。

23 宁以non-member、non_friend替换member函数

对于某些可以解耦的函数来说,定义为成员函数会使这些函数可以自由的访问私有成员变量,这是减少了封装性的,所以可以定义为非成员函数或者非友元函数(友元函数和成员函数访问权限相同)。C++中通用的方法是定义于namespace中,因为namespace可以跨文件进行扩展,所以我们可以将不同种类的这些函数放在不同的文件中,但是属于同一个namespace。所以在扩展功能的时候,只需要在namespace中建立头文件并声明函数就行(C++标准库函数的做法)。

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

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

class A{
private:
    int a;
    int b;
public:
    A(int _a = 0, int _b = 1) : a(_a), b(_b){ }
    int aa(){ return a; }
    int bb(){ return b; }
    const A operator(const A &aaa) const{
        return A(a * aaa.a, (b * aaa.b));
    }
};

A a;
A ret = a * 2;  //可以
A ret2 = 2 * a; //不可以

但是如果添加了非成员函数,就可以通过隐式类型转换,完成这种乘法运算(支持数据交换)[或者采用友元函数也可以]

class A{
    ...
};
const A operator*(const A &a, const B &b){
    return ret(a.aa() * b.aa(), a.bb() * b.bb());
}

A a;
A ret = a * 2;  //可以
A ret2 = 2 * a; //可以

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

对于用户自建类的swap操作,可以自行编写模版全特化的swap以增加运行速度

class Aimpl{
    int a, b, c;    //很多数据
    vector<double> vec; //复制时间很长
};
class A{
public:
    Aimpl *ptr;
};

对于这个自建类我们可以仅仅交换指针,所以可以自行扩充std名称空间(我们不被允许改变std命名空间内的任何东西,但可以为标准的模版(如swap())增加特化版本),增加为我们这个类的特化版swap<A>()

namspace std{
    template <>
    void swap<A>(A &lhs, A &rhs){
        swap(lhs.ptr, rhs.ptr);
    }
}

但是如果指针是private类型的呢,这函数就无法通过编译了,所以要换一个思路,即调用公有成员函数的思路,我们自定义一个名为swap()的public成员函数做真正的置换工作,然后特化swap,使其调用该成员函数。

class Aimpl{
    int a, b, c;    //很多数据
    vector<double> vec; //复制时间很长
};
class A{
private:
    T *ptr
public:
    ...
    void swap(A &other){
        using std::swap;    //使用标准库中的swap函数避免递归,但是用using声明可以使代码去搜索有没有用户自定的特化版本(见底下详细解释)
        swap(ptr, other.ptr);
        //不能写成std::swap(ptr, other.ptr);,否则编译器不能去找到自定义的特化版本,只会使用标准库的版本
    }
    ...
};

namespace{
    template <>
    void swap<A>(A &lhs, A &rhs){
        a.swap(b);  //调用公有成员函数
    }
}

但是这样做还是有个不足,那就是如果A和Aimpl是模版类的话,就不能按照这种方法写了:

template <typename T>
class Aimpl{
    ...
};
template <typename T>
class A{
    ...
};

namespace{
    template <T>
    void swap<A<T> >(A<T> &lhs, A<T> &rhs){ //方法错误,无法通过编译
        a.swap(b);  //调用公有成员函数
    }
}

这是因为函数模版只可以被全特化,不可以被偏特化,(只有类模板可以被全特化)原因是可以用重载的方法去代替偏特化。

这个时候还是可以声明一个非成员函数swap()让其调用成员函数swap(),但是非成员函数不再声明为特化版本,并放入用户自定义的命名空间内。

namespace AStuff{
    ...
    template <typename T>
    class A{ ... };
    ...
    template <typename T>
    void swap(A<T> &lhs, A<T> &rhs){
        a.swap(b);
    }
}

但是从客户的观点来看,我们写下一个模版函数

template <typename T>
void func(T &a, T &b){
    using std::swap;
    swap(T &a, T &b);
}

应该调用什么swap()函数呢?可能是一般版本或者是特化版本。

C++的名称查找法则确保找到globa作用于或T所在之命名空间内的任何T专属的swap,使用这种调用方式(使用using声明,并且调用swap的时候不加任何的命名空间修饰)

using std::swap;
swap(T &a, T &b);

如果T恰好是A并且位于命名空间AStuff中,编译器就会找到AStuff中特化的版本并使用,若未找到则调用一般版本。

使用这种调用方式(调用swap的时候加命名空间修饰std::)

std::swap(T &a, T &b);

会强迫编译器只认std内的swap(包括其任何模版全特化版本)。

猜你喜欢

转载自blog.csdn.net/u012630961/article/details/80225646