首页 > 编程语言 >Effective C++ 笔记

Effective C++ 笔记

时间:2023-01-12 12:56:35浏览次数:36  
标签:const 函数 Effective 笔记 static C++ 声明 class 定义

Effective C++ 笔记

Sec0 Introduction

  • 本书的目的:
    如何有效运用C++,使软件易理解、易维护、可移植、可扩充、高效、并有预期行为
  • 提出的忠告分两类:
    • 一般性的设计策略,带有具体细节的特定语言特性
      • 如何在两个不同做法中择一完成某项任务?
      • inheritance还是templates?
      • public还是private?
      • private继承还是composition?
      • 选择member函数还是non-member函数?
      • 选择pass-by-value还是pass-by-reference?
    • 更多的细节:
      • assignment操作符的适当返回类型
      • 何时该令析构函数为virtual?
      • 当operator new无法找到足够内存该怎么办?
  • 阅读本书的方式:
    • 从感兴趣的items开始

Sec1 Accustoming Yourself to C++

Item01: View C++ as a federation of languages

  • 现在的C++: Multiparadigm programming language
    一个同时支持procedural, object-oriented, functional, generic, metaprogramming的语言

  • 如何理解这样一个语言?
    将C++视为一个由相关语言组成的联邦而非单一语言。在其某个次语言(sublanguage)中,各种守则与通例都倾向于简单、直观易懂并且容易记住。
    主要的次语言有4个:

    • C
      blocks、statements、preprocessor、built-in data types、arrays、pointers等统统来自C
    • Object-Oriented C++
      即C with Classes 所诉求的:classes(析构函数和构造函数)、encapsulation、inheritance、polymorphism、virtual函数(动态绑定)等等。
    • Template C++
      即C++的泛型编程(generic programming) 部分。带来了崭新的编程范型(programming paradigm),也就是所谓的template metaprogramming(TMP)。
    • STL
      template程序库。定义了containers、iterators、algorithms和function objects。
  • Tips:
    C++高效编程守则视状况而变化,取决于你使用C++的哪个部分。

Iterm02: Prefer consts, and inlines to #defines

  • 使用常量来替换宏定义

    • 使用#define的坏处:

      #define ASPECT_RATIO 1.653
      
      • 可能编译器开始处理源码之前就被预处理器移走了。于是记号名称ASPECT_RATIO有可能没有进入记号表(symbol table)
      • 所以运用此常量但是获得编译错误信息的时候,难以定位。
    • 解决之道:用常量来替换宏

      const double AspectRatio = 1.653;
      
      • 可以进入记号表(symbol table)内。
  • 替换的两种特殊情况:

    • 定义常量指针(constant pointers)

      要在头文件定义一个常量cahr*-based字符串。必须写 const两次:

      const char* const authorName = "Scott Meyers";
      

      其实string对象更合适

      const std::string authorName("Scott Meyers");
      
    • class专属常量:
      为了将常量的作用域(scope)限制于class内,必须让它成为class的一个成员(member):而为确保此常量最多只有一份实体,你必须让它称为一个static成员:

      class GamePlayer {
      private:
          static const int NumTurns = 5;	// 常量声明式
          int scores[NumTurns];
          ...
      };
      
      • 注1:

        变量定义:用于为变量分配存储空间,还可为变量指定初始值。程序中,变量有且仅有一个定义。

        变量声明:用于向程序表明变量的类型和名字。

        定义也是声明:当定义变量时我们声明了它的类型和名字。

        extern关键字:通过使用extern关键字声明变量名而不定义它。

      • 注2:

        1.定义也是声明,extern声明不是定义,即不分配存储空间。extern告诉编译器变量在其他地方定义了。

        2.如果声明有初始化式,就被当作定义,即使前面加了extern。只有当extern声明位于函数外部时,才可以被初始化。

        例如:extern double pi=3.1416; //定义

        3.函数的声明和定义区别比较简单,带有{ }的就是定义,否则就是声明。

        4.除非有extern关键字,否则都是变量的定义。

      • 注3:程序设计风格:

        1. 不要把变量定义放入.h文件,这样容易导致重复定义错误。

        2. 尽量使用static关键字把变量定义限制于该源文件作用域,除非变量被设计成全局的。

        3. 可以在头文件中声明一个变量,在用的时候包含这个头文件就声明了这个变量。

      只要不取地址,可以声明并使用它们而无须提供定义式。
      但如果取某个class专属常量的地址,要看到定义式的话,需要这么写:

      const int GamePlayer::NumTurns;	// 定义
      

      因为class常量在声明时获得初值。因此定义时不可以再设初值。

    • 也可以用enum来代替const或者define。但是enum行为上更像一个define

  • 宏作为函数需要注意的地方:

    #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))
    int a = 5, b = 0;
    CALL_WITH_MAX(++a, b);	// 递增两次
    CALL_WITH_MAX(++a, b+10);	//递增一次
    

    所以建议用template inline函数:

    template<typename T>
    inline void callWithMax(const T& a, const T& b)
    {
        f(a > b ? a : b);
    }
    

    这是一个函数,所以遵守作用域和访问规则。】

  • Tips:

    • 对于单纯常量,最好以const对象或者enums替换#defines
    • 对于形似函数的宏(macros),最好改用inline函数替换#defines

Item03: Use const whenever possible

  • const的一些知识点:
    const出现在星号左边,表示被指物是常量。星号右边则表示指针自身为常量。

    • 对应到迭代器:
      因为迭代器其实就是像T*指针。所以声明迭代器为const就像声明指针为const一样,表示指针不能变。但是想要迭代器指向的数据不变,就得用const_iteraotr
  • const面对函数声明时的应用:

    • 令函数返回一个常量值
      避免无意义的赋值动作
  • const成员函数:
    是为了确认该成员函数可作用于const对象身上。

    • 它们使class接口比较容易被理解
    • 操作const对象成为可能

    一个小知识点:两个成员函数如果只是常量性的不同,可以被重载!

    TextBlock tb("Hello");
    std::cout << tb[0];	// 调用non-const TextBlock::operator[]
    const TextBlock ctb("Hello");
    std::cout << ctb[0];	// 调用const TextBlock::operator[]
    
    • mutable关键字:
      可以在const成员函数也可以更改成员变量!
  • 在const和non-const成员函数中避免重复(常量性转移 casting away constness)

    即:让non-const operator[]调用其const兄弟。可以避免代码重复!

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

    第一次转型:用static_cast<const TextBlock&>将*this转换。以可以调用const类型的[]。第二次是从const operator[]的返回类型值中移除const。

    • 注:不应该用const类型调用non-const类型!
  • 总结:

    • 将某些东西声明为const,可以帮助编译器侦测出错误用法,const可以被施加于作用域中的任何对象、函数参数、函数返回类型、成员函数本体。
    • 编译器强制实施bitwise constness,但写程序的时候应该使用概念上的常量性 conceptual constness。
    • 注意实现non-const和const版本的代码重复!

Item04: Make sure that objects are initialized before they're used

  • array(来自C part of C++) 不保证其内容被初始化,但是vector(来自STL part of C++)就保证初始化

  • 所以建议永远都使用初始化!

    • 对于构造函数,确保将每个成员初始化。
      而注意,在构造函数的{}里面进行赋值,并不是初始化!而是assignment!赋值!
      C++中,对象的成员变量的初始化动作发生在进入构造函数本体之前。最佳写法是:使用所谓的member initialization list。成员初始列替换赋值动作。从而构造函数本体不需要进行任何动作。
  • 成员初始化次序:
    base classes总是更早于derived classes被初始化的。
    而class的成员变量总是以其声明次序被初始化。

  • 不同编译单元内定义之 non-local static 对象的 初始化次序:

    • 注:local static对象指的是在函数里面的static对象。其他的,global对象、定义于namespace作用域内的对象、在classes内、在file作用域内被声明为static的对象称为 local static对象。
      non-local static对象,会在main()结束的时候调用析构函数自动销毁。

    客户机和本机的non-local static的初始化顺序很难决定,所以建议:

    • 将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static)。这些寒湖是返回一个reference指向它所含的对象,然后用户调用这些函数,而不涉及对象。即non-local static对象被local static对象替换了。
      这其实是Singleton模式的一个常见实现手法。可以保证获得的reference的对象指向一个历经初始化的对象。
    class FileSystem { ... };
    FileSystem tfs() {
        static FileSystem fs;
        return fs;
    }
    class Directory { ... };
    Directory::Directory( params ) {
        ...;
        std::size_t disks = tfs().numDisks();
        ...;
    }
    Directory& tempDir() {
        static Directory td;
        return td;
    }
    
    • 这种函数第一行定义并初始化一个local static对象,第二行返回它。挺适合inlining的。

      但是在多线程下可能有麻烦!解决方法是,在单线程启动阶段(single-threaded startup portion) 手工调用所有reference-returning 函数。

  • 总结:

    • 为内置型对象手工初始化
    • 使用成员初值列。少在构造函数里卖弄使用赋值操作。注意次序!
    • 用local-static对象替代non-local static对象。

Sec2 Constructors, Destructors, and Assignment Operators

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

  • default 构造函数和析构函数
    编译器产生的构造函数是个non-virtual。除非这个class的base class自身声明有virtual析构函数。

  • 如果在一个内含reference成员的class内支持赋值操作(assignment),必须自己定义copy assignment操作符!内含const成员也一样。

    或者,如果某个base classes将copyassginment操作符声明为private,编译器将拒绝为其derived classes生成一个copy assignment操作符。

  • 总结:
    编译器可以暗自为class创建default构造函数、copy构造函数、copy assignment操作符和析构函数
    但是要注意特殊情况。

Item06: Explicitly disallow the use of compiler-generated functions you do not want

  • 所有编译器产出的函数都是public。为组织创建,我们得自行声明它们。所以我们可以将copy构造函数或者copy assignment函数声明为private。借此可以组织自动创建。但是这也不太安全!

    • 更好的做法:

      class HomeForSale {
      public:
          ...;
      private:
          ...;
          HomeForSale(const HomeForSale&);	// 只有声明
          HomeForSale& operator=(const HomeForSale&);
      };
      

      这里就不需要写函数参数的名称了。反正也不会用。有了这样的定义,如果客户企图拷贝对象,编译器会报错,如果member函数或friend函数这么做,连接器会报错。
      这里可以将连接器错误移动到编译器,只需要将这些声明移动到private里面就行。

  • 为了阻止HomeForSale对象被拷贝,我们也可以将它继承Uncopyable:

    class HomeForSale : private Uncopyable {
        ...;
    }
    

    这里,class就不需要声明copy构造函数或者class assign操作符了。 相对更简单一些。

  • 总结:
    为驳回编译器自动生成的情况,可以将相应的成员函数声明为private并且不予实现。使用像Uncopyable 这样的base class也是一种做法

Item07:Declare destructors virtual in polymorphic base classes

标签:const,函数,Effective,笔记,static,C++,声明,class,定义
From: https://www.cnblogs.com/orangestar/p/17046317.html

相关文章

  • 【学习笔记】缓存
    缓存1.什么是缓存缓存是存在内存中的临时数据。将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用去磁盘(关系型数据库数据文件)上查询,从缓存中查询,从而提高查询效......
  • C++ 使用 new 创建二维数组
    C++使用new创建二维数组最直接的方法就是 ​​newT[M][N]​​​。返回的指针类型是 ​​T(*)[N]​​​,它是指向数组的指针,可以直接使用数组下标形式访问元素。释放内......
  • 【C++11】chrono库
    chrono是c++11中的时间库,提供计时、时钟等功能学习chrono,关键是理解里面精度、时间段、时间点的概念1.精度时钟节拍(时间精度),后面的时间段和时间点都是基于精度的计算......
  • OpenGL ES 2.0编程指导阅读笔记(二)你好,三角形:OpenGL ES 2.0示例
    本章覆盖以下内容:用EGL创建屏上表面加载顶点和片元着色器创建程序对象,附加顶点和片元着色器,并链接程序对象设置视点清除colorbuffer渲染一个简单图元使colorbuff......
  • OpenGL ES 2.0编程指导阅读笔记(三)EGL介绍
    EGL能够管理绘图表面。EGL提供了以下机制:和本地窗口系统进行通信;查询可用的绘图表面类型和配置;创建绘图表面;在OpenGLES2.0和其他图形渲染API之间同步渲染;管理渲染......
  • 5月TIOBE编程语言:Python持续第一,C++将冲击前三
    新的TIOBE5月编程语言榜单出炉了,让我们一起看一下这次有哪些新看点:Python稳居第一,C++或将冲至Top3本次的榜单和4月相比没有明显变化,Top5依然是Python、C、Java、C++......
  • CONTINUAL LEARNING IN VISION TRANSFORMER--阅读笔记
    CONTINUALLEARNINGINVISIONTRANSFORMER---阅读笔记摘要:​ 持续学习的目标是从新数据中持续学习新任务,同时保留过去学习的任务的知识。最近,利用最初在计算机视觉自然......
  • CQF学习笔记M1L2二叉树模型
    https://blog.csdn.net/weixin_42859140/article/details/107018914?spm=1001.2101.3001.6650.5&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCo......
  • 读编程与类型系统笔记05_函数类型
    1. 策略模式1.1. 在运行时从一组算法中选择某个算法1.1.1. 封装一组算法1.1.2. 在运行时使用其中一个算法1.2. 把算法与使用算法的组件解耦1.3. 面向对象实现......
  • C++ STL的简单应用(vector容器专题)
    #include<iostream>#include<string>#include<stdlib.h>#include<vector>//#include<algorithm>usingnamespacestd;//vector容器的简单应用voiddemo1(){......