写出更有效的C++代码

本文是《Effective C++》的读书笔记,作者强调将C++视为由C、Object-Oriented C++、Template C++、STL四个次语言组成的联邦,并给出了关于构造、析构、赋值运算、资源管理、设计与声明、实现、继承与面向对象设计、模板与泛型编程、定制new和delete等多个方面的最佳实践。例如,推荐使用const、enum、inline替换#define,尽量使用const,明智地处理继承与多态,利用smart pointers管理资源,以及理解和使用模板的技巧。文章提醒开发者注意编译器自动生成的函数,避免异常逃离析构函数,以及理解new和delete的替换机制等关键知识点。
摘要由CSDN通过智能技术生成

写在前面

读了读《Effective C++》甚至感觉这本书就是专门给我这种弟弟写的,本文也是此书的阅读笔记

正文

一、习惯C++

作为一个写了两年C#和Shader被商业引擎惯坏了的程序员,看见老朋友甚至有点陌生

01:把C++当做一个语言联邦

把C++当做由四个次语言组成的联邦,从一个次语言到另一个次语言时,守则可能改变。
这四个次语言分别是:C、Object-Oriented C++、Template C++、STL

02:尽量以const、enum、inline 替换 #define

#define ASPECT_RATIO 255

开发者极有可能被它所带来的编译错误感到困惑,编译器可能提到255而不是ASPECT_RATIO,也许该语句被其他预处理干掉,追踪它会浪费时间。
解决办法就是用常量替换宏

const int AspectRatio = 255;

着重说明

  1. 由于常量经常定义于头文件内,因此有必要将指针(而不是指针所指之物)声明为const

    const char* const authorName = "TOSHIRON";
    
  2. 对于Class专属常量,为了确保他只有一份实体,必须使其成为static成员

    class GamePlayer
    {
         
    	private:
    		static const int NumTurns = 5;
    		...
    }
    
  3. 如果不想让别人获取到指向某个常量的指针,因为取const地址是合法的,所以可以用enum取代

    class GamePlayer
    {
         
    	private:
    		enum{
          NumTurns = 5 };
    		...
    }
    
  4. 用内联函数替代宏,以获得相同的效率和功能

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

03: 尽可能使用 const

const出现在*左边,被指物是常量
const出现在*右边,指针自身是常量
const出现在*两边,指针和所指事物都是常量

着重说明

  1. 令一个函数返回一个常量值,往往可以降低因客户错误而造成的意外,而又不至于放弃安全性和高效性
    我感觉这几乎主要是为了单独避免一种情况

    class Rational{
         ...};
    const Rational operator* (const Rational& lhs, const Rational& rhs);
    ...
    Rational a,b,c;
    ...
    if(a*b = c){
         ...}
    

    找错麻烦?因为常量不允许赋值所以会直接报错

  2. 利用常量性(constness)不同,重载函数

    class TextBlock
    {
         
    	public:
    		...
    		const char& operator[] (std::size_t position) const //第二个const是其重载的依据
    		{
         
    			return text[positon];
    		}
    		char& operator[] (std::size_t position)
    		{
         
    			return text[position];
    		}
    	private:
    		std::string text;	
    }
    
    TextBlock tb("Hello");
    std::cout << tb[0];		//调用non-const
    
    Const TextBlock ctb("World");
    std::cout << ctb[0];	//调用const
    
  3. const成员函数不可以更改对象内任何non-static成员变量;解决办法就是用 mutable 关键字修饰,使变量总是可更改的,及时在const成员函数内。

  4. 在 const 和 non-const 成员函数中避免重复,常量性重载往往伴随着大量重复代码,这时,我们需要让non-const 利用 const 函数。

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

    首先,将 *this 转型为 const,调用const成员函数,再移除const

04: 确定对象在使用前已经初始化

这是当然了,至少我使用过的任何编程语言都有要求这一点

int x;

x有可能被初始化为0

class Point
{
   
	int x, y;
};
...
Point p;

p的成员变量有时候会初始化为0,有时候不会,所以手动初始化很有必要。

着重说明

  1. C++规定,对象的成员变量初始化动作发生在进入构造函数本体之前。

    class PhoneNumber{
         ...};
    class ABEntry
    {
         
    	public:
    		ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones);
    	private:
    		string theName;
    		string theAddress;
    		list<PhoneNumber> thePhones;
    		int numTimeConsulted;
    		ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones)
    		{
         
    			theName = name;
    			theAddress = address;
    			thePhones = phones;
    			numTimesConsulted = 0;
    		}
    

    书上的说法是 构造函数中那四行四赋值,而不是初始化
    构造函数应该改为

    ABEntry(const string& name, const string& address, const list<PhoneNumber>& phones)
    	:theName(name), theAddress(address), thePhones(phones), numTimesConsulted(0)
    {
          }
    
  2. 为解决两个不同编译单元内的初始化次序问题,使用local static 替代 non-local

    class FileSystem
    {
         
    	public: 
    	...
    	size_t numDisks() const;
    	...
    };
    extern FileSystem tfs;
    

    替代为

    class FileSystem
    {
         
    	public: 
    	...
    	size_t numDisks() const;
    	...
    };
    FileSystem& tfs()
    {
         
    	static FileSystem fs;
    	return fs;
    }
    

    这样,在调用时才不用在乎初始化顺序的问题

二、构造、析构、赋值运算

因为GC的存在,很长时间没有用析构函数了

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

编译器可以暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符,以及析构函数。

class Empty{
   };

相当于

class Empty
{
   
	public:
		Empty(){
   ...}
		Empty(const Empty& rhs){
   ...}
		~Empty(){
   ...}
		Empty& operator = (const Empty& rhs){
   ...}
};

着重说明

  1. 当对一个“内含reference 成员”或“内含const 成员”进行赋值操作时,编译器自己生成的赋值重写无法完成此工作,需要自己专门写。
  2. 如果把赋值重写设为private,那更调用不了。

06: 如果不想用编译器自动生成的函数,就应该明确拒绝

如果想要驳回编译器自动提供的函数,可以将成员函数声明为 private 并且不与实现。

class Uncopyable
{
   
	protected:
		Uncopyable(){
   }
		~Uncopyable(){
   }
	private:
		Uncopyable(const Uncopyable&);
		Uncopyable& operator = (const Uncopyable&);
};

然后之后的类可以继承Uncopyable,反正C++可以多继承,但是多继承会阻止empty base class optimization,慎重

class Abc:private Uncopyable{
   ...}

07: 为多态基类生命 virtual 析构函数

class TimeKeeper
{
   
	public:
		TimeKeeper();
		virtual ~TimeKeeper()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值