EC++学习笔记(四) 设计与声明

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

必须考虑客户可能做出什么样的错误(防御式编程)
std:shared_ptr会自动使用它的"每个指针专属的删除器",消除了"cross-DLL problem"(对象在DLL中被 new 创建,却在另一个DLL中 delete 销毁)

 

条款19:设计 class 犹如设计 type 类型系统
条款20:宁以 pass-by-reference-to-const 代替 pass-by-value

C语言永远是pass-by-value,C++默认是pass-by-value,函数参数都是以实际参数的副本拷贝为初值,函数调用的结果是函数返回值的一个副本拷贝
注意C语言中传指针方式,传指针具有引用语义,对象复制以后并没有分离,而是共享关联同一资源
采用pass-by-reference-to-const 效率高(无对象拷贝),是多态前提(基类引用),也可以避免基类和派生类之间的对象切割

 

条款21:必须返回局部对象时,禁止返回其reference
const Rational& operator*(const Rational& lhs, const Rational& rhs) {
    Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
    return result;
}

这个函数返回一个reference指向result,但result是local对象,local对象在函数退出前已经被销毁了!!!

任何函数如果返回一个reference指向某个local对象都将一败涂地
任何函数如果返回一个指针指向某个local对象同样将一败涂地

const Rational& operator*(const Rational& lhs, const Rational& rhs) {
    static Rational result;
    result = ...;
    return result;
}

这个函数返回static对象,不具有多线程安全性,并行计算时极易出错

必须返回对象时,就让函数pass-by-value返回一个新对象

inline const Rational operator*(const Rational& lhs, const Rational& rhs) {
    Rational result(lhs.n * rhs.n, lhs.d * rhs.d);
    return result;
}

这个函数以pass-by-value方式返回值语义对象,值语义对象复制之后两者分离,所以函数结束后获得的仍然是正确的对象

总结:绝不要返回reference或pointer指向一个local对象

 

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

客户唯一能够访问对象成员变量的方法就是通过存取函数get()和set()
成员变量的封装性与"成员变量的内容改变时所破坏的代码量"成反比

条款23:宁以non-member、non-friend 替换member函数(本人不认同)
条款24:操作符重载时必须选择设置为类成员还是普通的非成员函数

操作符重载时,选择成员还是非成员实现有一些指导原则:
1.赋值(=)、下标([])、调用( () )、成员访问(->)必须定义为类成员,否则编译出错
2.复合赋值操作符(+=)通常定义为类成员
3.对称操作符(算术运算符、关系运算符)通常选择为non-member函数实现
注:操作符选择类成员实现时,显式形参数比操作数数目少1,因为当操作符为成员函数时,this 指向左操作数

 

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

STL提供的标准swap算法如下:

namespace std {
    template<typename T>
    void swap(T& a, T& b) {
        T temp(a);
        a = b;
        b = temp;
    }
}

缺省版本设计到对象复制,有时效率低下。但是考虑如下:

class WidgetImpl{
public:
    ...
private:
    int a, b, c;
    std::vector<double> v;
};

class Widget{
public:
    Widget(const Widget& rhs);
    Widget& operator=(const Widget& rhs) {
        ...
        *pImpl = *(rhs.pImpl);
    }
private:
    WidgetImpl* pImpl;
};

一旦置换两个Widget对象,唯一需要做的就是置换其pImpl指针,可以考虑模板特化技术

namespace std{
    template<>
    void swap<Widget>(Widget& a, Widget& b) {
        swap(a.pImpl, b.pImpl); //目前还无法编译通过
    }
}

上述代码中 template<> 表示它是 std:swap的一个全特化版本,函数名之后的<Widget>表示这一特化版本只针对"T是Widget"而设计(偏特化)
上述代码不能通过编译,因为a.pImpl和b.pImpl都是 private,所以进行如下改进:

class Widget{
public:
    ...
    void swap(Widget& other) {    //绝不可以抛出异常
        using std::swap;          //逼迫编译器使用std::swap(因为偏特化版本swap还未实现完成)
        swap(pImpl, other.pImpl); //此时使用的是std::swap
    }
}

namespace std{
    template<>
    void swap<Widget>(Widget& a, Widget& b) {
        a.swap(b);
    }
}

一旦编译器看到对swap调用时,就会查找适当的swap调用,如果没有T专属之swap存在,编译器就会使用std内的swap

std::swap(obj1, obj2);    //错误的swap调用方式,强迫编译器总是调用std内部的swap版本
swap(obj1, obj2);         //正确的swap调用方式,不带任何的命名空间修饰

 c++只允许对类模板进行偏特化,不能对函数模板进行偏特化

posted @ 2013-11-26 14:25  skyline09  阅读(318)  评论(0编辑  收藏  举报