Effective C++笔记(更新中...)

Effective C++

55 Specific Ways to Impove Your Programs and Designs

改善程序与设计的55个具体做法

豆瓣读书

 

 

1. Accustoming Yourself to C++ 让自己习惯C++

01: View C++ as a federation of languages

视C++为一个语言联邦

C++最初名称 C with Classes

主要的次语言有四个:

  • C:区块、语句、预处理、数据类型、数组、指针
  • Object-Oritented C++:对象、封装、继承、多态、动态绑定
  • Template C++ :泛型编程
  • STL:容器、迭代器、算法、函数对象

每个次语言有自己的规约

 

02: Prefer consts, enums and inlines to #defines

尽量以const,enum,inline替换#define

  • 以编译器替换预处理器,因为 #define不是语言的一部分,编译器看不到被#define定义的变量,没有进入记号表(symbol table),编译错误时被替换,难以追踪问题

  • 指针类型常量,写两次const: const char* const authorName = "Scott Meyers";

    最好使用string类型:const std::string authorName(“Scott Meyers”)

  • 类常量,声明时获取初值:static const

  • 旧编译器不支持声明获取初值,使用enumenum {Num = 5};

    enum类似#define,无法获取常量地址,没有分配存储空间

    模板元编程

  • template inline函数替换宏,使用宏注意加括号,但仍无法避免MAX(++a, b)形式调用的不确定结果

  • #incline需求降低了,但没有完全消除。#ifdef/#ifndef控制编译

 

03: Use const whenever possible.

尽可能使用const

  • const与指针

    • 星号左边,const char* p:被指物是常量(*p)
    • 星号右边,char* const p:指针自身是常量(p)
    • 星号两边,const char* const p:被指物和指针都是常量
    • const Widget* pw 与 Widget const *pw相同
  • const与函数

    • 返回值 const
    • const参数:除非需要修改参数
    • const成员函数:不改动对象
  • bitwise/phisical constness与logical constness

    class CTextBook {
    public:
        ...
        std::size_t length() const 
        {
            if (!isLengthValid) {
                textLength = std::strlen(pText);  // 错误:在只读结构中不能有赋值操作
                isLengthValid = true;
            }
    
            return textLength;
        }
    private:
        char*       pText;
        std::size_t textLength;
        bool        isLengthValid;
    };
    
    • logical constness实现方式,使用mutable释放掉non-static成员变量的bitwise constness约束:
    class CTextBook {
    public:
        ...
        std::size_t length() const 
        {
            if (!isLengthValid) {
                textLength = std::strlen(pText);
                isLengthValid = true;
            }
    
            return textLength;
        }
    private:
        char*  		  pText;
        mutable std::size_t textLength;
        mutable bool 	  isLengthValid;
    };
    
    • 如果operator[]包含边界检查、访问信息、数据完整性检验,编写const和non-const方法会有大量重复代码

      将相同代码移动到同一个成员函数,多了函数调用和两次return

    • 常量性转移(casting away constness)

      non-const调用const函数

    class TextBook {
    public:
        ...
        char& operator[](std::size_t position) const  // const对象
    	{
    		... // 边界检查
    		... // 访问信息
    		... // 数据完整性检验
    		return text[position];
    	}
    	char operator[](std::size_t position)
    	{
    		return const_cast<char&>(				// 将operator[]返回值的const转除
    			static_cast<const TextBook&>(*this)[position]	// 为*this加上const,调用const函数
    		);
    	}
    private:
        std::string text;
    };
    
    • 不能反向进行,即让const版本函数调用non-const函数。调用const方法必须保证对象的逻辑状态不被修改,non-const有更改的风险

 

04: Make sure that objects are initialized before they’re used

确定对象被使用前已先被初始化

读取未初始化的值会导致不明确的行为:某些平台可能会让程序终止运行;读入半随机的bits,导致不可预知的程序行为

  • 内置类型,定义时手动初始化/读入

    int x = 0;

    const char* text = “A C-style string”;

    double d;

    std::cin >> d;

  • 赋值(assignment)与初始化(initialization)

    错误做法,构造函数采用了赋值而非初始化:

    class PhoneNumber { ... };
    class Entry {
    private:
        std::string theName;
        std::string theAddress;
        std::list<PhoneNumber> thePhones;
        int numCount;
    public:
        Entry(const std::string& name, const std::string& address,
            const std::list<PhoneNumber>& phones);
    }
    
    Entry::Entry(const std::string& name, const std::string& address,
            const std::list<PhoneNumber>& phones)
    {
        theName = name;
        theAddress = address;
        thePhones = phones;
        numCount = 0;
    }
    
  • C++规定,对象的成员变量的初始化动作发生在进入构造函数本体之前。使用成员初始化列表:

    Entry::Entry(const std::string& name, const std::string& address,
            const std::list<PhoneNumber>& phones)
        :theName(name),
        theAddress(address),
        thePhones(phones),
        numCount(0)
    { }
    

    结果与上面相同,但效率更高

    default构造函数+调用copy赋值操作符 VS 调用一次copy构造函数

    • 赋值的版本,首先调用默认构造函数,为theName,theAddress,thePhones设初值,然后再赋新的值

    • 初始化列表使用copy构造函数,(参数可为空,调用默认构造函数)

  • 成员变量是const或reference,一定要使用初值

  • 成员初始化总是以声明次序被初始化,与初始化列表顺序无关

  • non-local static对象的初始化顺序,C++无明确定义

    至少两份源码文件,在其中一次引用静态对象,使用前可能未初始化

    解决方法:单例模式

    将每个non-local static对象在专属函数内声明为static,函数返回其引用指向的对象。用户调用函数而不是直接使用对象,转化为local static对象。tfs ==> tfs()

    class FileSystem { ... };
    FileSystem tfs()
    {
    	static FileSystem fs;
    	return fs;
    }
    
    // 客户使用
    class Directory { ... };
    Directory::Directory( params )
    {
    	...
    	std::size_t disks = tfs().numDisks();
    	...
    }
    

    额外问题:非const的静态对象,多线程中的竞争条件(race conditions),即多个线程同时访问相同的共享数据,造成数据的不一致性

 

2.Constructors, Destructors, and Assignment Operators 构造/析构/赋值运算

05: Know what functions C++ silently writes and calls

了解C++默默编写并调用哪些函数

posted @ 2020-08-23 22:14  izcat  阅读(306)  评论(0编辑  收藏  举报