Effective(Modern)C++笔记01

2023-11-02

Effectice C++01

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

  • C

区块(blocks)、语句(statements)、预处理(preprocessor)、内置数据类型(built-in data types)、数组(arrays)、指针(pointers)

  • Object-Oriented C++

classes(包括构造和析构函数)、封装(encapsulation)、继承(inheritance)、多态(polymorphism)、virtual(动态绑定)

  • Template

泛型编程、模板元编程

  • STL

容器、迭代器、算法、函数对象

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

以编译器替换预处理器

#define ASPECT_ROTIO 1.653 // 可能并未记录到符号表 获取编译错误信息会提到 1.653
						   // 目标码会出现多份1.653
const double AspectRotio = 1.653
// 常量指针
const char* const authorName = "Scott Meyers"
    
// class 专属常量
class GamePlayer {
private:
    // 通常需要在外面有定义式
    // static 且为整数类型 int char bool 只要不取它们的地址,可以提供声明式而无需提供定义式
    static const int NumTurn = 5; //声明常量=======================================
    int scores[NumTUr];
}

const int GamePlayer::Numturns; // Numturns的定义 放进一个实现文件
class GamePlayer {
private: 
    enum {NumTurns = 5}; // the enum hack 无法取地址
    int score[NumTruns];
}
  • 对于单纯常量,最好以const对象或enum替换#define
  • 对于形似函数的宏,最好改用inline函数替换#define

条款03:尽可能使用const

STL迭代器以指针为根据塑模出来,所以迭代器的作用就像一个T*指针。声明迭代器为const对象就像声明指针为const一样(即声明一个T* cosnt指针)——不得指向不同的东西,它所指的东西的值是可以改变的。

const T*指针 => const_iterator

const 可以和函数返回值、各参数、函数自身(成员函数)产生关联

const和non-const成员函数中避免重复

令non-const版本调用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&>( // 返回const对象 转化为非const对象
        static_cast<const TextBlock&>(*this) // 转化为const对象 为调用cosnt函数
        [position]
        );
    }
}

条款04:确定对象使用之前已被初始化

C part of C++ 不保证发生初始化

array(来自C part of C++)不保证其内容被初始化, vector(STL part of C++)却有此保证

除内置类型,初始化责任落在构造函数(constructors)身上

别混淆了赋值(assignment)和初始化(initialization)

class PhoneNumber{ //... };
class ABEntry {	// 通讯簿
public:
    ABEntry(const std::string& name, const std::string& address,
           const std::list<PhoneNumber>& phones);
private:
    std::string theName;
    std::string theAddress;
    std::list<PhoneNumber> thePhones;
    int numTimesConsulted;
}// 首先调用default构造函数为theName、theAddress和thePhones设初值
// 然后立即再对他们赋予新值 浪费了default构造函数做的一切
AbEntry::ABEntry(const std::string& name, const std::string& address,
           const std::list<PhoneNumber>& phones);
    {
        theName = name; // 这些都是赋值(assignments)
        theAddress = address; // 而非初始化 (initializations)
        thePhones = phones;
        numTimesConsulted = 0; // 内置对象,初始化和赋值的成本相同
    }
AbEntry::ABEntry(const std::string& name, const std::string& address,
           const std::list<PhoneNumber>& phones) // 现在都是初始化
    :theName(name),
	theAddress(address),
	thePhones(phones),
	numTimesConsulted(0)
{ }

初始化次序:

base classes更早与其derived被初始化

class的成员变量总是以其声明的次序初始化

static对象,其寿命从被构造出来直到程序结束为止

函数内的static对象称为local static对象,其它的static对象称为non-local-static对象

class FileSystem {
public:
    std::size_t numDisks() const;
    //...
};
static FileSystem tfs; //预备给客户使用的对象
class Directory {
public:
    Directiry(params);
    // ...
};

Directory::Directory(params) {
    //...
    
    std::size_t disks = tfs.numDisks(); // 使用tfs对象
}

// 在不同的编译单元 无法保证 tfs在tempDir之前被初始化
Directory tempDir(params);

解决方案:用local static对象替换non-local static对象

class FileSystem {//...}
FileSystem& tfs() {
    static FileSystem fs;
    return fs;
}

Directory& tempDir() {
    static Directory td;
    return td;
}
  • 为内置对象进行手工初始化,因为C++不保证初始化它们
  • 构造函数最好使用成员初值列。初值列中的成员变量,其排列次序应该和它们在class中的声明次序相同
  • 为避免跨单元之初始化次序, 以local static 对象替换non-local static对象

构造/析构/赋值运算

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

M条款17:理解特种成员函数的生成机制

  1. 唯有这些函数被需要(被调用),它们才会被编译器创建出来
  2. 编译器产出的析构函数是个non-virtual
class Empty {};

class Empty {
public: // inline
    Empty() {……}                          // default ctor
    Empty(const Empty& rhs) {……}		  // copy ctor
    ~Empty() {……}						  // dctor
    
    Empty& operator=(const Empty& rhs) {……} // copy=
}
//C++11
class Widget {
public:
    ……
    Widget(Widget&& rhs);// 移动构造函数
    Widget& operator=(Widget&& rhs); // 移动赋值运算符
}
  • 并不能保证移动操作真的会发生。“按成员移动”实际上更像是按成员的移动请求,因为那些不可移动的型别(C++98遗留),将通过复制移动实现“移动”。
  • 两种复制操作是彼此独立的:声明了其中一个,并不会阻止编译器生成另一个。
  • 两种移动操作并不彼此独立。——原因在于自己声明的一个move函数与编译器自动生成的有不同,另一个极有可能与编译器自动生成的不同。——涉及到资源的管理方式
  • 一旦显式的声明了复制操作,这个类也就不会再生成移动操作了。——成员适用于复制操作,极有可能不适用移动操作。
  • 一旦声明了移动操作,编译器就会废除复制操作。——理由同上

三大律

C++98: 如果你声明了copy-ctor、copy=、dctor中的任意一个,你就得同时声明所有的这三个。——如果有改写复制操作的要求,往往意味着该类需要执行某种资源管理。

推论:如果用户声明了析构函数,则复制操作就不该自动生成。C++98未严格遵守;C++11:只要用户声明了dctor函数,就不会生成移动操作。

移动操作的生成条件(如果需要生成)

  • 该类未声明任何复制操作
  • 该类未声明任何移动操作
  • 该类未声明任何析构函数

C++11:在已经存在复制操作或析构函数的条件下,仍然自动生成复制操作已经成为了被废弃的行为。

成员函数模板在任何情况下都不会抑制特种成员函数的生成。

// 编译器会始终生成Widget的复制和移动操作
class Widget {
    ……
    template<typename T>
    Widget(const T& rhs);
    
    template<typename T>
    Widget operator=(const T& rhs);
}

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

M条款11:优先选用删除函数,而非private未定义函数

C++98为了阻止一些特种成员函数被使用,采用的做法是声明其未private,并且不去定义它们。

//C++98
template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
    ……
private:
	basic_ios(const basic_ios& ); //not defined
    basic_ios& operator=(const basic_ios& ); // not defined
}
// 链接期才会诊断出错误
//C++11
template <class charT, class traits = char_traits<charT> >
class basic_ios : public ios_base {
public:
    ……
public:
	basic_ios(const basic_ios&= delete;
    basic_ios& operator=(const basic_ios& ) = delete;
}

任何函数都能成为删除函数

bool isLucky(int number);

// 下面的调用都可以
if (isLucky('a'));
if (isLucky(true));
if (isLucky(3.5));

//如果幸运数必须是整数
bool isLucky(int number);
bool isLuncky(char) = delete;
bool isLuncky(bool) = delete;
bool isLuncky(double) = delete; // 拒绝double和float型别 float优先选择转型为double

指针中的两个异类:

void* 无法对其执行提领、自增、自减操作

char* 表示的C风格的字符串

template<typename T>
void processPointer(T* ptr);

template<>
void processPointer<void*> = delete;
template<>
void processpointer<char*> = delete;

模板特化必须在名字空间作用域而非类作用域内来撰写

//C++98
class Widget {
public:
    ……
    template<typename T>
	void processPointer(T* ptr)
    {……}
///
private:
    template<>
    void processPointer<void>(void); // 错误=======================
///
}

template<>
void Widget::processPointer<void> = delete;

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

class TimeKeeper {
public:
    TimeKeeper();
    ~TimeKeeper();
    ……
};

class AtomicClock: public TimeKeeper {……}// 原子钟
class WaterClock: public TimeKeeper {……}// 水钟
class WristWatch: public TimeKeeper {……}// 腕表

// 工厂函数 返回的对象必须位于heap中
TimeKeeper* getTimeKeeper();

Timekeeper* ptk = getTimeKeeper();
……
delete ptk; // 依赖客户执行delete动作,基本上便带有某种错误倾向。

derived class对象经由一个bass class指针被删除。——若base classer的析构函数为non-virtual,结果未定义,实际执行时通常发生的是对象的derived成分没有被销毁。

任何class只要带有virtual函数都几乎确定应该也有一个virtual析构函数。

只有当class内至少含一个virtual函数,才将它声明为virtual析构函数

Classes的设计目的如果不是作为base classes使用,或不是为了具备多态性,就不该声明virtua析构函数。

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

C++不喜欢析构函数吐出异常。

//class负责数据库连接
class DBConnection {
public:
    ……
    static DBConnection create();
    void close(); // 关闭连接,失败则抛出异常
};
// 用来管理DBConnection资源的class
class DBConn {
public:
    ……
    ~DBConn() { db.close();} // 确保数据库连接总是会被关闭 
}; // 析构函数传播异常,允许它离开这个析构函数,会出现难以驾驭的麻烦

两种解决方案:

// close抛出异常就结束程序。通常通过abort完成
DBConn::~DBConn() {
    try{db.close();}
    catch (……) {
    	制作运转记录,记下对close的调用失败
        std::abort();
    }
}
// 吞下因调用close而发生的异常
DBConn::DBConn() {
    try {db.close();}
    catch (……){
        制作运转记录,记下对close的调用失败。
    }
}

让客户自己调用close函数,对可能出现的问题作出反应

class DBConn {
public:
    ……
    void close() {db.close(); close = true;}
    ~DBConn() {
        if (!closed) {
            try { db.close();}
            catch(……) {记录下来并结束程序, 或吞下异常 }
        }
    }
private:
    DBConnection db;
    bool closed;
}
  • 析构函数绝不要吐出异常。如果析构函数调用的函数可能抛出异常,析构函数应该捕抓任何异常,然后吐下它们或结束程序。
  • 如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(而非在析构函数中)执行该操作。

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

class Transaction { // 所有交易的base classes
public:
    Transaction();
    virtual void logTransaction() const = 0; // 做出一份因类型不同而不同的日志记录
};

Transaction::Transaction() {
    ……
    logTransaction();
}
class BuyTransaction : public Transaction { // derived class
public:
    virtual void logTransaction() const; // 记录此类交易日志
    ……
};

class SellTransaction : public Transaction { // derived class
public:
    virtual void logTransaction() const; // 记录此类交易日志
    ……
};

Buytransaction b; ——首先Transaction构造函数调用,调用的是Transaction::logTransaction的版本。 ——base classes构造期virtual函数绝不会下降到derived classes阶层。

在base class 构造期,virtual函数不是virtual函数。在derived class 对象的 base class 构造期间,对象的类型是base class 而不是 derived class。对象在derived class 构造函数开始执行前不会成为一各derived class 对象。

解决方案

class Transaction {
public:
    explict Transaction(const std::string& logInfo);
    void logTransaction(const std::string& logInfo) const; // 如今是个non-virtual函数
  ……
}
Transaction::Transaction(const std::string& logInfo) {
    ……
    logTransaction(logInfo);
};
class BuyTransaction : public Transaction {
public:
    BuyTransaction(parameters)
        : Transaction(createLogString(parameters))
        {……}
private:
    static std::string createLogString(parameters);
};

你无法使用virtual函数从base classes向下调用,在构造期间,你可以借由 令drived classes 将必要的构造信息向上传递base class 构造函数替换之加以弥补。

在构造期和析构期不要调用virtual函数,因为这类调用从不下降至derived class。

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

为了实现连续赋值

class Widget {
public:
    ……
    Widget& operator=(const Widget& rhs) {
        ……
        return *this; // 返回左侧对象
    }
    ……
};

适用于所有赋值相关运算

class Widget {
public:
    ……
    Widget& operator+=(const Widget& rhs) {
        ……
        return *this;
    }
    Widget& operator=(int rhs) {
        ……
        return *this;
    }
};

条款11:在operator中处理"自我赋值"

// 自我赋值
class Widget {……};
 Widget w;
……
w = w;
a[i] = a[j] // 潜在的自我赋值
*px = *py;
class Bitmap {……};
class Widget{
    ……
private:
    Bitmap* pb;
}

//================================
Widget& Widget::operator(const Widget& rhs) {
    delete pb; // 如果 *this 和 rhs是同一各对象 则数据被销毁无法再恢复
    pb = new Bitmap(*rhs.pb);
    return *this;
}

传统做法:证同测试

Widget& Widget::operator=(const Widget& rhs) {
    if (this == &rhs) return *this;
    
    delete pd;
    pb = new Bitmap(*rhs.pb); // 如果抛出异常 pb会指向一块被删除的Bitmap;
    return *this;
}

另一种做法:

Widget& Widget::operator=(const Widget& rhs) {
    Bitmap* pOrig = pb;
    pb = new Bitmap(*rhs.pb); // 如果抛出异常 pb恢复原状
    delete pOrig;
    return *this;
}

copy and swap技术

class Widget {
    ……
    void swap(Widget& rhs); // 交换*this和rhs的数据
};

Widget& Widget::operator=(const Widget& rhs) {
    Widget temp(rhs);
    swap(temp);
    return *this;
}
  • 确保对象自我赋值的时operator=有良好的行为。其中技术包括比较“来源对象”和“目标对象”的地址、精心周到的语句顺序、以及copy-and-swap
  • 任何函数如果可以操作一个以上的对象,而其中多个对象是同一个对象时,其行为仍然正确。

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

class Customer {
public:
    Customer(const Customer& rhs);
    Customer& operator=(const Customer& rhs);
private:
    std::string name;
}

Customer::Customer(const Customer& rhs):name(rhs.name) {}
Customer& Customer::operator=(const Custormer& rhs) {
    name = rhs.name;
    return *this;
}
class Date {……};
class Customer {
public:
    ……
private:
    std::string name;
    Date lastTransaction; // 如果copy函数不改变,lastTransaciton就不会被复制,编译器也不会提醒
}

一旦发生继承

class PriorityCustomer: public Customer {
public:
    PriorityCustomer(const PriorityCustomer& rhs);
    PriorityCustomer& operator=(const PriorityCusomer& rhs);
private:
    int priority;
}

//复制了PriorityCustomer声明的成员变量,但是Customer的成员变量却未被复制。
PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
    : priority(rhs.priority)
    {}
PriorityCustome& PriorityCustomer::operator=(const PriorityCustomer& rhs) {
    priority = rhs.priority;
    return *this;
}

解决方案:

PriorityCustomer::PriorityCustomer(const PriorityCustomer& rhs)
    : Customer(rhs), //调用base class的copy构造函数
	priority(rhs.priority)
    {}

PriorityCustome& PriorityCustomer::operator=(const PriorityCustomer& rhs) {
    Customer::operator=(rhs); // 对base class 成分进行赋值动作
    priority = rhs.priority;
    return *this;
}

copy=操作符调用copy构造函数是不合理的——这就像在试图构造一个已经存在的对象

同样copy-ctor函数调用copy=同样无意义。

如果copy-ctor和copy=有相近的代码,消除重复的方法是,建立一个新的成员函数给两者调用,这样的函数往往是private而且常被命名未init。

  • Copying函数应该确保复制 对象的所有成员变量 以及 所有base class 成分。
  • 不要尝试以某个copy函数实现另一copying函数。应该将共同机能放进第三个函数中,并由两个copying函数共同调用。

条款20:pass-by-reference-to-constpass-by-value

slicing(对象切割)

class Window {
public:
	std::string name() const;  // 返回窗口的名称
    virtual void display() const; // 显示窗口和其内容
}

class WindowWithScrollBars: public Window {
public:
    virtual void display() const;
}

// pass by value
void printNameAndDisplay(Window w) {
    std::cout << w.name();
    w.dispaly();
}

// w 会构造一个Window对象,总是会调用Window::dispaly()
WindowWithScrollBars wwsb;
printNameAndDisplay(wwsb);

// 正确方法 pass by reference to const
void printNameAndDisplay(const Window& w);
  • 尽量以pass-by-reference-to-const 替换 pass-by-value。前者通常比较高效,并可避免切割问题
  • 内置类型,以及STL的迭代器函数对象 pass-by-value比较恰当。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Effective(Modern)C++笔记01 的相关文章

随机推荐

  • git 配置

    git config global user name username username 是自己的账户名 git config global user email username email com username email com
  • 并发容器(一):普通容器&&同步容器&&并发容器

    前言 之前我们学习过了集合 并发编程 现在我们来学习并发容器 在并发编程中 经常听到Java集合类 同步容器 并发容器 那么他们之间有哪些分类 优劣呢 我们先把这个框架给分清楚了 这样后面学习的时候不会乱 集合容器 大家熟知的集合类Arra
  • 学习docker基础——docker存储驱动、docker开发镜像基础

    目录 一 docker存储驱动及其选择 二 使用overlay2存储驱动 三 迁移docker根目录 四 docker存储的挂载类型 五 开发docker镜像 一 docker存储驱动及其选择 一 概述 1 docker存储驱动与联合文件系
  • 理解Docker(5):Docker 网络

    本系列文章将介绍 Docker的相关知识 1 Docker 安装及基本用法 2 Docker 镜像 3 Docker 容器的隔离性 使用 Linux namespace 隔离容器的运行环境 4 Docker 容器的隔离性 使用 cgroup
  • 【模拟电路】仪表放大器分析

    仪表放大器电路的典型结构如图1所示 它主要由两级差分放大器电路构成 其中 运放A1 A2为同相差分输入方式 同相输入可以大幅度提高电路的输入阻抗 减小电路对微弱输入信号的衰减 差分输入可以使电路只对差模信号放大 而对共模输入信号只起跟随作用
  • 离线安装ceph集群(ceph-13.2.10)

    记录 332 场景 在CentOS 7 9操作系统上 使用ceph的rpm mimic的ceph 13 2 10安装ceph集群 应用ceph对象存储 ceph object store 应用ceph块设备 ceph block devic
  • 【视频解读】Window上安装和使用autogluon V0.7

    1 使用conda安装的python环境 教程使用的是极简版miniconda 由于我们的电脑中安装了anaconda 所以不需要进行进一步安装 python版本为3 9 博客里面有anaconda和python版本的对应关系 注意查看版本
  • conda create -n python 3.6_conda create 怎么创建纯净的 Python3.6 环境?

    刚接触 conda 安装了 Anaconda3 想用 conda create 命令创建一个只含标准模块和几个必需第三方模块 如 pip wheel 的 Python3 6 环境 使用命令 conda create n test pytho
  • 核心思想_[转载]用最浅显的语言解释佛法的核心思想:缘起性空(即般若)

    本文是在 用最浅显的语言解释佛法的核心思想 缘起性空http tieba baidu com p 3074939669 一文基础上 重新修改而成 有兴趣的可以对比 参考原文 从缘起性空 到三界唯心 万法唯识 到涅槃寂静 直到大乘如来藏思想
  • 13天带你了解C++ ---DAY10 C++之vector

    目录 1 string容器 2 构造函数和析构函数的相关操作 3 迭代器 4 容量相关 5 元素访问相关 6 元素遍历相关 7 元素操作相关 8 vector模拟实现 1 string容器 vector容器是C 标准模板库提供的管理任意元素
  • mysql导入csv中文数据乱码问题分析与解决

    摘要 解决csv文件向mysql导入含有中文数据 导入后中文出现乱码问题 结论 在导入含中文字符时注意两个问题 第一 告诉Mysql文件的编码是什么 第二 数据库表中的列编码要设置成支持中文的字符集 导入源数据 SQL代码 LOAD DAT
  • mysql语句

    各种sql语句 一 引言 二 sql基础语句 1 sql新增语句 2 sql查询语句 3 sql修改语句 4 sql删除语句 5 sql其他语句 5 1 sql倒序 正序查询 5 2 sql表行数查询 三 sql较有深度语句 1 sql主从
  • Linux ip地址报错(Temporary failure in name resolution)活检地址检查

    1 项目健康检查报错 报错信息 java net UnknownHostException Temporary failure in name resolution 解决办法 hostname 查看主机名 hostname i 查看本机对应
  • PHPstorm必备插件推荐

    1 env files support 对 env 文件的支持 2 ignore 对 ignore 文件的支持 3 Ideolog 对 log 文件的支持 4 Chinese Simplified Language Pack 中文语言包 官
  • 抖音视频号情感类短视频爆火,背后有什么特点?有什么值得借鉴

    有一些人渐渐不联系了 不是因为淡了远了 而是没有合适的身份陪伴 没有合适的理由联络 没有合适的机会见面 只能放在心里偶尔回忆 经常想念 明明只是一张动图 但却可以让人自动脑补了早期抖音情感博主在线念白的腔调 没错 这些曾经在抖音快手风靡一时
  • 阿里犸良导出的json文件怎么使用

    犸良是什么 犸良作为一站式动效制作平台 通过海量的动效素材以及可视化编辑能力 帮助零基础的用户轻松完成动效制作 支持全平台iOS Android H5 小程序 无论是营销展位 活动页面 空状态还是产品icon 让动效更简单 官网地址 htt
  • pear-admin-flask开源后台模板(适合于毕设)

    Pear Admin Flask 开 箱 即 用 的 Flask 快 速 开 发 平 台 预 览 官 网 群聊 社区 项目简介 Pear Admin Flask 基于 Flask 的后台管理系统 拥抱应用广泛的python语言 通过使用本系
  • C# 如何向String[]字符数组插入数据

    C 如何向String 字符数组插入数据 思路 了解 由于数组是非动态的 不能进行动态的添加 思路 首先将string 字符数组转换成list 第二给list添加数据 最后把list转换成string 数组 String arrs new
  • 怎么上传文件到spark服务器上,从SFTP服务器加载文件到spark RDD

    您可以以下方式使用spark sftp库在你的程序 火花2 x的 Maven的依赖 com springml spark sftp 2 11 1 1 0 SBT依赖 libraryDependencies com springml spar
  • Effective(Modern)C++笔记01

    Effectice C 01 条款01 视C 为一个语言联邦 C 区块 blocks 语句 statements 预处理 preprocessor 内置数据类型 built in data types 数组 arrays 指针 pointe