Effective C++ 第三版 所有条款总结及对应代码描述(纯干货)

Effective C++ 第三版 条款总结及对应代码描述

条款1: 视C++为一个语言联邦

C++可以被看作是几种不同编程语言的集合:C、Object-Oriented C++、Template C++、以及STL。每种"子语言"都有其优势和用法。例如,C部分提供了对硬件的低级访问,面向对象部分支持封装和继承,模板部分支持泛型编程,STL则提供了一套强大的库。理解这些部分的不同规则和最佳实践对于编写高效、可维护的代码至关重要。

// C-style
int array[10];

// Object-Oriented C++
class Widget {
   
public:
    void draw() const; // 类方法
};

// Template C++
template<typename T>
void swap(T& a, T& b) {
   
    T temp = a;
    a = b;
    b = temp;
}

// STL
#include <vector>
std::vector<int> vec;

条款2: 尽量以const、enum、inline替换#define

使用预处理指令(#define)定义常量和宏可能导致调试困难,因为预处理器只是简单的文本替换,并不检查类型等。相反,使用const关键字可以确保类型安全,使用enum和inline可以替代宏,提供更清晰、更安全的代码。

// bad
#define ASPECT_RATIO 1.653

// good
const double AspectRatio = 1.653;

条款3: 尽可能使用const

使用const可以提高代码的可读性,减少编程错误,因为它指定了变量不可修改。应用const到各种适当的场合,包括函数参数、返回类型、成员函数本身,确保程序的正确性和清晰性。

void f(const Widget& w) {
   
    // w.doSomething(); // 这样是错误的,如果doSomething()不是const成员函数
    w.show() const; // 正确
}

class Widget {
   
public:
    std::size_t size() const {
    return data.size(); }
private:
    std::vector<int> data;
};

条款4: 确保对象使用前已先被初始化

在C++中,未初始化的变量可能导致随机运行时错误。最好的做法是在构造对象时立即给它赋值。在构造函数初始化列表中初始化字段比在构造函数体内赋值更为高效。

class Widget {
   
public:
    Widget(int i, bool b) : id(i), valid(b) {
   } // 初始化列表
private:
    int id;
    bool valid;
};

条款5: 了解C++默默编写并调用哪些函数

C++编译器会自动为类生成默认构造函数、析构函数、拷贝构造函数和拷贝赋值运算符,如果未明确声明这些。理解这一行为对于设计类及其行为至关重要,尤其是涉及资源管理(如动态内存分配)时。

class Widget {
   
public:
    Widget(const Widget& rhs) : id(rhs.id) {
   } // 拷贝构造函数
    Widget& operator=(const Widget& rhs) {
       // 拷贝赋值运算符
        id = rhs.id;
        return *this;
    }
private:
    int id;
};

条款6: 若不想使用编译器自动生成的函数,就该明确拒绝

在C++中,如果你不提供拷贝构造函数和拷贝赋值运算符,编译器会为你自动生成。但有时候,你并不希望你的对象被拷贝,例如,当你的类包含了对资源如文件句柄或网络连接的独占控制时。在这种情况下,你应该阻止生成这些函数,可以通过将它们声明为private并且不提供实现来实现:

class Uncopyable {
   
protected:  // 允许派生类构造和析构
    Uncopyable() {
   }
    ~Uncopyable() {
   }
private:    // 阻止拷贝构造函数和赋值运算符
    Uncopyable(const Uncopyable&);
    Uncopyable& operator=(const Uncopyable&);
};

class MyResource: private Uncopyable {
   
    // 现在MyResource类不能被拷贝
};

条款7: 为多态基类声明virtual析构函数

当一个类用作基类,并且通过基类的指针或引用来管理派生类对象时,应当声明一个虚析构函数。这确保了在通过基类指针删除一个派生类对象时,可以正确地调用派生类的析构函数,避免资源泄露。

class Base {
   
public:
    virtual ~Base() {
   
        // 基类的析构函数
    }
};

class Derived : public Base {
   
public:
    ~Derived() {
   
        // 派生类的资源清理
    }
};
Base* b = new Derived();
delete b;  // 正确调用Derived的析构函数,然后是Base的析构函数

条款8: 别让异常逃离析构函数

在析构函数中抛出异常是非常危险的,因为如果在析构过程中已经因为另一个异常而处于堆栈展开过程中,抛出另一个异常将导致程序终止。因此,析构函数应该捕获并处理所有异常,或者避免调用可能抛出异常的函数。

class ResourceManager {
   
public:
    ~ResourceManager() {
   
        try {
   
            // 尝试释放资源,可能抛出异常
            releaseResource();
        } catch(...) {
   
            // 处理异常,确保不逃离析构函数
            handleError();
        }
    }
private:
    void releaseResource();
    void handleError();
};

条款9: 绝不在构造和析构过程中调用virtual函数

在构造或析构的过程中调用虚函数不会调用到派生类中覆盖的版本。这是因为在构造和析构过程中,对象的动态类型是正在构造或析构的类。如果调用虚函数,它会调用当前类层次结构中的那个版本,这可能不是你期望的行为。

class Base {
   
public:
    Base() {
    call(); }
    virtual ~Base() {
    call(); }
    virtual void call() {
    std::cout << "Base::call()\n"; }
};

class Derived : public Base {
   
public:
    void call() override {
    std::cout << "Derived::call()\n"; }
};

// 在main中创建Derived对象
// 输出将是 "Base::call()" 而不是 "Derived::call()"
Derived d;

条款10: 令operator=返回一个reference to *this

为了实现连续赋值,赋值运算符应该返回一个指向当前对象的引用。这允许链式赋值,并且是对赋值操作符的一个常见和期望的实现。

class Widget {
   
public:
    Widget& operator=(const Widget& rhs) {
   
        // 检查自赋值
        if (this == &rhs) return *this;
        
        // 复制数据
        data = rhs.data;
        return *this; // 使赋值可以链式进行
    }
private:
    int data;
};

Widget a, b, c;
a = b = c;  // 链式赋值

条款11:在 operator= 中处理“自我赋值”

当设计赋值运算符时,必须考虑到对象可能会将自身赋值给自身的情况。如果不妥善处理,这种自我赋值可能会导致程序错误,比如意外的资源释放。这可以通过检查赋值运算符的参数是否与当前对象相同来防止。

class Widget {
   
public:
    Widget& operator=(const Widget& rhs) {
   
        if (this == &rhs) return *this; // 自我赋值检查
        // 释放旧资源
        delete[] data;
        // 复制赋值的数据
        data = new int[rhs.size];
        std::copy(rhs.data, rhs.data + rhs.size, data);
        size = rhs.size;
        return *this;
    }
private:
    int* data;
    std::size_t size;
};

条款12:复制对象时勿忘其每一个成分

在实现拷贝构造函数和赋值运算符时,确保复制对象的所有成员变量和基类部分。忽略任何成员或基类的复制都可能导致运行时错误或对象状态不一致。

class Base {
   
public:
    int b;
};

class Derived : public Base {
   
public:
    Derived& operator=(const Derived& rhs) {
   
        Base::operator=(rhs);  // 复制基类部分
        d = rhs.d;             // 复制派生类部分
        return *this;
    }
private:
    int d;
};

条款13:以对象管理资源

使用对象来管理资源(如动态内存、文件句柄、网络连接等),利用C++的构造函数和析构函数,可以自动化资源的获取和释放,避免资源泄漏。这通常通过实现一个RAII(Resource Acquisition Is Initialization)类来完成。

class FileHandler {
   
public:
    FileHandler(const char* filename) {
   
        file = fopen(filename, "w")
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Warren++

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值