• Effective Cpp


    1 让自己习惯C++

    Accustoming Yourself to C++

    1.1 视C++为一个语言联邦

    (1) C
    (2)Object-Oriented C++
    (3) Template C++
    (4)STL

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

    【使用常量替换宏定义

    const double AspectRatio = 1.653;
    
    • 1

    The enum hack

    class GamePlayer {
    private:
    	enum{NumTurns = 5};
    	
    	int scores[NumTurns]
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    【使用inline函数替换宏定义】
    对此《C++ Primer Plus》有详细解释。

    1.3 尽可能使用const

    char greeting[] = "Hello";
    char* p = greeting;             // non-const pointer, no-const data.
    const char* p = greeting;       // non-const pointer, const data.
    char* const p = greeting;       // const pointer, non-const data.
    const char* const p = greeting; // const pointer, const data.
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果const出现在 * 左边,则被指向的Object是常量。
    如果const出现在 * 右边,则指针本身是常量。

    【const 修饰函数参数】

    void f1(const Widget* pw);
    void f2(Widget const * pw);
    
    • 1
    • 2

    f1和f2的参数是等价的。都是常量指针。

    【const 修饰函数返回值】
    防止如下事情出现

    if(a * b = c) {
    }
    
    • 1
    • 2

    【const修饰成员函数】

    class TextBlock {
    public:
    	// operator [] for const object
    	const char& operator[](std::size_t position) const {
    		return text[position];
    	}
    	// operator [] for non-const object.
    	char& operator[](std::size_t position) {
    		return text[position];
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    TextBlock tb("hello");
    std::cout << tb[0]; // call non-const []
    const TextBlock ctb("World");
    std::cout << ctb[0]; // call const []
    
    • 1
    • 2
    • 3
    • 4
    cout << tb[0]; // ok. read for non-const TextBlock.
    tb[0] = 'x'; // ok. write for non-const TextBlock.
    cout << ctb[0]; // ok. read for const TextBlock.
    ctb[0] = 'x'; // error! write for const TextBlock.
    
    • 1
    • 2
    • 3
    • 4

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

    class TextBlock {
    public:
    	// operator [] for const object
    	const char& operator[](std::size_t position) const {
    		// do sth.
    		return text[position];
    	}
    	// operator [] for non-const object.
    	char& operator[](std::size_t position) {
    		return const_cast<char&>(static_cast<const TextBlock&>(*this)[position]);
    	}
    	
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    static_cast:为(*this)添加const,指明调用的是const版本的函数,避免递归不退出。
    const_cast: 为返回值移除const。

    【编译器强制实施bitwise constness,但是程序员在编程的时候应该使用的是conceptual constness】

    1.4 确保对象被使用前已经被初始化

    【推荐使用成员初值列(member initializing list)的方式实现构造函数】

    ABEntry::ABEntry(string& name, string& address, list<PhoneNumber> phones)
    :theName(name), // 这些都是在初始化
    theAddress(address),
    thePhones(phones),
    numTimesConsulted(0) {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    【C++对“定义于不同编译单元内的non-local static对象”的初始化次序没有明确定义】
    C++保证,函数内的local static对象会在“该函数调用期间”“首次遇上该对象之定义式”时被初始化。

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    2 构造、析构和赋值运算

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

    class Empty {}
    
    • 1

    等价于

    class Empty {
    public:
    	Empty() {} // default构造函数
    	Empty(const Empty& rhs) {} // copy构造函数
    	~Empty() {} // 析构函数
    
    	Empty& operator=(const Empty& rhs) {} // copy assignment操作符
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

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

    【将copy构造函数和copy assignment声明为private】

    class HomeForSale {
    public:
    private:
    	HomeForSale(const HomeForSale&);
    	HomeForSale& operator=(const HomeForSale&);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    正常情况下,试图copy HomeForSale object,编译报错。如果不慎在member函数或者friend函数中进行拷贝,会在链接期报错。

    【设计不可拷贝的基类】

    class UnCopyable {
    protected:
    	UnCopyable() {} // 允许derived对象构造和析构
    	~UnCopyable() {}
    private:
    	UnCopyable(const UnCopyable&); // 阻止copy
    	UnCopyable& operator=(const UnCopyable&);
    }
    
    class HomeForSale : private UnCopyable {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    2.3 为多态基类声明virtual 析构函数

    任何class只要带有virtual函数都几乎可以确定也应该有一个virtual的析构函数。 防止Object的局部销毁。

    当一个class不打算作为一个base class,就不应该将析构函数声明为virtual的。防止将对象的size变大。

    经验: 只有当class内至少有一个virtual函数的时候,才为这个class声明virtual 析构函数。

    Pure virtual 函数导致抽象类。为希望成为抽象类的class声明一个pure virtual的析构函数。必须为纯虚的析构函数提供定义,否则连接过程会出现问题。

    2.4 别让异常逃离析构函数

    class DbConn {
    public:
    	void close() {  // 客户可以手动close数据库连接
    		db.close();
    		closed = true;
    	}
    
    	~DbConn() {
    		if (!closed) {
    			try {
    				db.close();			// 兜底机制,关闭数据库连接。	
    			} catch(...) {
    			 	// process exception. 
    			 	...   // 如果关闭动作失败,记录下来并结束程序或者吞下异常。
    			}
    		}
    	}
    private:
    	DbConnection db;
    	bool closed; // 记录是否手动关闭异常了。 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    2.5 绝不在构造和析构过程中调用virtual函数

    Base class构造期间virtual函数不会下降到derived class。“在base class构造期间,virtual函数不是virtual函数”。

    解决方案如下:

    class Transaction {
    public:
    	explict Transaction(const std::string& logInfo);
    	void logTransaction(const std::string& logInfo);	// non-virtual function.
    };
    
    Transaction::Transaction(const std::string& logInfo) {
    	...
    	logTransaction(logInfo); // non-virtual调用
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    class BuyTransaction: public Transaction {
    public:
    	BuyTransaction(parameters) 
    	: Transaction(createLogString(parameters)) { // 将log信息传递给base class构造函数。 
    	}
    private:
    	static std::string createLogString(parameters);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    2.6 令operator=返回一个reference to *this

    因为有人喜欢使用连锁形式的赋值。

    int x,y,z;
    x = y = z = 15; // 连锁形式赋值
    
    • 1
    • 2
    class Widget {
    public:
    	Widget& operator=(const Widget& rhs) {
    		...
    		return *this;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.7 在operator=中处理“自我赋值”

    class Bitmap {};
    
    class Widget {
    private:
    	Bitmap* pb;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    【证同测试(identity test)】

    Widget& Widget::operator=(const Widget& rhs) {
    	if (this == &rhs) {
    		return *this;
    	}
    
    	delete pb;
    	pb = new Bitmap(*rhs.pb);
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    【实现“异常安全性”】
    实现了“异常安全性”往往自动可以达到“自我赋值安全”。

    Widget& Widget::operator=(const Widget& rhs) {
    	Bitmap* pOrigin = pb;
    	pb = new Bitmap(*rhs.pb);
    	delete pOrigin;
    	
    	return *this;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.8 复制对象时勿忘其每一个成分

    copying函数指拷贝构造函数和operator=。

    当编写一个copying函数的时候,请确保:(1)复制所有的local成员变量。(2)调用所有base classes内的适当的copying函数。

    如果copy构造函数和operator=的代码几乎相同,请抽取私有公共方法。

    3 资源管理

    3.1 以对象管理资源

    void f() {
    	std::shared_ptr<Investment> pInv(createInvestment()); 
    }
    
    • 1
    • 2
    • 3

    获得资源之后立即放到管理对象(managing object)内。“资源取得的时机便是管理对象的初始化时机”(Resource Acquisition is Initialization,RAII)。
    管理对象(managing object)运用析构函数确保资源被释放。

    3.2 在资源管理类中小心copying行为

    class Lock {
    public:
    	explicit Lock(Mutex* pm): mutextPtr(pm) {
    		lock(mutextPtr);
    	}
    	~Lock() {
    		unlock(mutextPtr);
    	}
    private:
    	Mutex* mutexPtr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    Mutex m;
    {
    	Lock m1(&m); // 锁定互斥器
    }
    Lock m2(m1); // 复制会发生什么事呢?
    
    • 1
    • 2
    • 3
    • 4
    • 5

    【禁止复制】

    class Lock : private Uncopyable {
    }
    
    • 1
    • 2

    【使用引用计数法】

    class Lock {
    public:
    	explicit Lock(Mutext* pm)
    	:mutextPtr(pm, unlock) { // 指定unlock为删除器
    		lock(mutexPtr.get());
    	}
    private:
    	std::tr1::shared_ptr<Mutex> mutexPtr;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    【复制底部资源】
    deep copying
    【转移底部资源的拥有权】
    资源的拥有权从被复制物转移到了目标物。

    3.3 在资源管理类中提供对原始资源的访问

    • API往往要求访问原始资源(raw resources),所以每一个RAII class应该提供一个“取得其所管理的资源”的办法。
    • 可以提供显示转换或者隐式转换。一般而言,显示转换比较安全,隐式转换对客户比较方便。

    3.4 使用new和delete时要采取相同的形式

    【尽量不要对数组形式做typedef动作】

    typedef std::string AddressLine[4]; // 每个人的地址有4行,每行是一个string。
    
    • 1
    std::string* pal = new AddressLines; // new AddressLines返回的是string*,就像new string[4]一样
    delete pal; // 行为未有定义
    delete[] pal // 正确的
    
    • 1
    • 2
    • 3

    使用STL中的集合类就可以达到同样的效果。

    请记住:

    • 如果在new表达式中使用[], 那么在delete表达式中也要使用[]。反之亦然。

    3.5 以独立语句将newed对象置入智能指针

    int priority();
    void processWidget(std::tr1::shared_ptr<Widget> pw, int priority);
    
    processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority); // 可能出现资源泄漏
    
    • 1
    • 2
    • 3
    • 4

    c++编译器以什么样的顺序完成参数的准备是不确定的。
    如果按照以下顺序,就有可能发生问题。
    (1)new widget
    (2)调用priority
    (3)调用shared_ptr的构造函数。
    如果priority函数的调用发生了异常,new Widget的指针将会遗失,并未置入智能指针中。

    解决办法就是将语句分开

    std::tr1::shared_ptr<Widget> pw(new Widget)
    processWidget(pw, priority);
    
    • 1
    • 2

    4 设计与声明

    4.1 让接口容易被正确使用,不易被误用

    【类型系统】

    class Date {
    public:
    	Date(int month, int day, int year);
    };
    
    Date d(30, 3, 1995); // 应该是3月30号
    Date d(2, 30, 1995); // 2月哪里有30号呀
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    struct Day {
    	explicit Day(int d):val(d) {}
    	int val;
    }
    
    class Date {
    public:
    	Date(const Month& m, const Day& d, const Year& y);
    };
    
    Date d(30, 3, 1995); // 不正确的参数类型
    Date d(Day(30), Month(3), Year(1995)); // 不正确的参数类型
    Date d(Month(3), Day(30), Year(1995)); // 正确
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    class Month {
    public:
    	static Month Jan() {return Month(1);} // 函数返回有效月份
    	static Month Feb() {return Month(2);}
    	...
    	static Month Dec() {return Month(12);}
    private:
    	explicit Month(int m); // 阻止生成新的月份
    };
    
    Date d(Month::Mar(), Day(30), Year(1995));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    【除非有好理由,否则应该令你的types行为与内置的types一致】
    【使用智能指针作为返回值】

    std::tr1:shared_ptr<Investment> createInvestment();
    
    std::tr1::shared_ptr<Investment> createInvestment() {
    	std::tr1::shared_ptr<Investment> retVal(static_cast<Investment*>(0), getRidOfInvestment);
    	retVal = ...; // 令retVal指向正确的对象。
    	return retVal;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    4.2 设计class犹如设计type

    设计规范:
    【新type的对象应该如何被创建和销毁?】
    影响到构造函数和析构函数,内存分配释放函数。
    【对象的初始化和对象的赋值有什么差别?】
    【新type的对象如果被passed by value,意味着什么?】
    copy构造函数用来定义一个type的pass-by-value应该如何实现。
    【什么是新type的合法值?】
    class必须维护的约束条件;
    成员函数(特别是构造函数、赋值操作符和setter)进行的错误检查;
    函数抛出的异常,以及函数异常明细(exception specifications)。
    【新type要配合某个继承图系(inheritance graph)吗?】
    如果是继承某个父类的,会受到父类的设计约束,特别是virtual或者non-virtual的影响。
    如果是允许派生的,这回影响类内声明的函数,尤其是析构函数——因为要考虑析构函数是否被设计成virtual的。
    【新type需要什么样的转换?】

  • 相关阅读:
    京东AIGC实战项目复盘;第一门AI动画系统课程;百川智能启动2024校园招聘;Kaggle 2023 AI前沿报告 | ShowMeAI日报
    B端产品实战课读书笔记:第七章B端产品常用功能设计
    Python之文件与文件夹操作及 pytest 测试习题
    LeetCode常见题型——图
    【javaScript面向对象-模块化-面相关对象编程——行走的方块案例】
    Docker启动故障问题 no such file or directory解决方法
    班级管理系统
    PMP_第9章章节试题
    Java Matcher.lookingAt()方法具有什么功能呢?
    Golang 递归获取目录下所有文件
  • 原文地址:https://blog.csdn.net/kaikai_sk/article/details/133136563