Effective C++ 学习笔记

条款03:尽可能使用const

对成员函数的const,有两个流行概念:bitwise constness(又称physical constness)和logical constness。

bitwise constness(物理上const,二进制位const)

class CTextBlock {
public:
    ...
    char& operator[](std::size_t position) const {
        return pText[position];
    }
private:
    char* pText;
};

这个函数虽然的确不改变类中任何非静态成员的值,但实际上我们仍然可以用此函数返回的引用修改到该类的对象本身。如

const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J';

如此,虽然我们设置了一个常量对象cctb,并且只调用了它的const成员函数,但是终究还是改变了它的值。

loginal constness(逻辑上const)

认可逻辑上const的人们认为一个const成员函数可以修改它处理的对象的某些bits,但只有在客户端侦测不出(不知道发生了这件事)才可以如此。如下的CTextBlock类有可能cache文本区块的长度以便应付询问(快速回答):

class CTextBlock {
public:
    ...
    std::size_t length() const;
private:
    char* pNext;
    std::size_t textLength; // 最近一次计算的文本区块长度
    bool lengthIsValid;     // textLength是否还有效
};
std::size_t CTextBlock::length() const {
    if (!lengthIsValid) {
        textLength = std::strlen(pText); // const成员函数内赋值不被允许
        lengthIsValid = true; // const成员函数内赋值不被允许
    }
    return textLength;
}

然而上面的代码不满足物理上的const,编译器不允许,会编译错误。虽然这两个数据的修改对const CTextBlock对象而言可以接受。解决方法是加mutable(可变的)关键字,它释放掉非静态成员变量的物理const(bitwise constness)约束:

class CTextBlock {
public:
    ...
    std::size_t length() const;
private:
    char* pNext;
    mutable std::size_t textLength; // 最近一次计算的文本区块长度
    mutable bool lengthIsValid;     // textLength是否还有效
};
std::size_t CTextBlock::length() const { ... } // 同上

non-const成员函数调用const函数提高代码复用性

class TextBlock {
public:
    ...
    const char& operator[](std::size_t position) const {
        ...
        return pText[position];
    }
    char& operator[](std::size_t position) {
        return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
    }
}

条款13:以对象管理资源

RAII

“以对象管理资源”(如智能指针)的观念常被称为:RAII(Resource Acquisition Is Initialzation,资源取得时机便是初始化时机)。

RAII对象们在构造函数中获得资源,在析构函数中释放资源。

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

C++只允许对类模板偏特化,不允许对函数模板的偏特化

如:

template <typename T>
class Widget {
    ...
};

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

编译错误信息:

error: non-type partial specializtion 'swap<Widget<T> >' is not allowed

条款28:避免返回handles指向对象内部成分

  • handles:引用、指针、迭代器(可以用来取得某个对象)
  • 对象内部成分:成员变量、不被公开使用的成员函数。

避免返回成员变量的非const引用

会导致可以通过const成员函数修改到对象内部,见条款3的例子。避免返回成员变量的非const引用,帮助const成员函数的行为像个const。

避免返回成员变量的const引用

class Point { ... };
struct RectData {
    Point ulhc; // upper left-hand corner
    Point lrhc; // lower right-hand corner
};
class Rectangle {
public:
    ...
    const Point& upperLeft() const { return pData->ulhc; }
    const Point& lowerRight() const { return pData->lrhc; }
    ...
private:
    std::shared_ptr<RectData> pData;
}
class GUIObject { ... };
const Rectangle boundingBox(const GUIObject& obj);

// 用户有可能如下使用:

GUIObject* pgo;
...
const Point* pUpperLeft = &(boundingBox(*pgo).upperLeft());

然而在最后一行语句结束之后,boundingBox函数返回的const Rectangle临时对象已经被析构。所以这个指针就是一个空悬指针。

避免返回不被公开使用的成员函数

不被公开使用的成员函数也就是被声明为protected或private者。如果某成员函数返回不被公开使用的成员函数,那么后者的实际访问级别就会提高至与前者同级。

条款29:为“异常安全”而努力是值得的

异常安全函数的三种保证:基本承诺、强烈保证、不抛异常保证

  • 异常安全函数(Exception-save functions)即使发生异常也不会泄漏资源或允许任何数据结构败坏。这样的函数区分为三种可能的保证:基本承诺、强烈保证、不抛异常保证
    • 基本承诺(basic guarantee):如果异常被抛出,程序内的任何事物仍然保持在有效状态下。(但有可能处于任何状态——只要是个合法状态)
    • 强烈保证(strong guarantee):如果异常被抛出,程序状态不改变。也就是说如果函数成功,就完全成功;如果函数失败,则程序会恢复到调用之前的状态。
    • 不抛异常保证(nothrow guarantee):承诺绝不抛出异常,因为它们总是可以完成它们原先承诺的功能。
  • “强烈保证”往往能够以 copy-and-swap 实现出来,但“强烈保证”并非对所有函数都可实现或具备现实意义(比如完成它们的代价太高)
  • 函数提供的“异常安全保证”通常最高只等于其所调用之各个函数的“异常安全保证”中的最弱者。(除非这个函数做了一些措施来提高调用的函数的异常安全保证,比如通过备份把基本承诺变为了强烈保证)

条款34:区分接口继承和实现继承

声明纯虚函数、非纯虚函数、非虚函数的目的

  • 声明一个pure virtual函数的目的是为了让derived classes只继承函数接口
  • 声明简朴的(非纯)impure virtual函数的目的是让derived class继承该函数的接口和缺省实现
  • 声明non-virtual函数的目的是为了令derived classes继承函数的接口及一份强制性实现

“pure virtual函数必须在derived classes中重新声明,但它们也可以拥有自己的实现”:通过对一个pure virtual函数一份定义:

class AirPlane {
public:
    virtual void fly(const Airport& destination) = 0;
    ...
};

void AirPlane::fly(const Airport& destination) {
    ...
}

class ModelA: public AirPlane {
public:
    virtual void fly(const Airport& destination) {
        AirPlane::fly(destination);
    }
    ...
}

class ModelC: public AirPlane {
public:
    virtual void fly(const Airport& destination) {
        自己的实现...
    }
    ...
}

条款46:需要类型转换时请为模板定义非成员函数

当编写一个class template,而它所提供之“与此template相关的”函数支持“所有参数之隐式类型转换”时,请将那些函数定义为“class template”内部的friend函数。

条款47:请使用traits classes表现类型信息

为什么不把这些信息直接放在对应的类里呢?

“traits必须能够施行于内置类型”意味“类型内的嵌套信息(nesting information)”这种东西出局了,因为我们无法将信息嵌套于原始指针内。因此类型的traits信息必须位于类型自身之外。

条款48:认识template元编程(TMP)

Template metaprogramming(TMP,模板元编程)可将工作从运行期移往编译期,因而得以实现早期错误侦测和更高的执行效率。

#include <iostream>

template<unsigned n>
struct Factorial {
    enum { value = n * Factorial<n-1>::value };
};

template<>
struct Factorial<0> {
    enum { value = 1 };
};

int main() {
    std::cout << Factorial<10>::value; // 3628800
    return 0;
}
posted @ 2020-03-09 23:15  SandyChn  阅读(241)  评论(0编辑  收藏  举报