《Effective C++》笔记

《Effective C++:改善程序与设计的55个具体作法》 by Scott Meyers

明智选择+精心设计

软件设计:“令软件做出你希望它做的事情”的步骤和做法,所谓最佳设计,取决于系统希望做什么事。

解决一个设计问题的方法不止一种,要训练自己思考多种方法。

声明(declaration):告诉编译器某个东西的名称和类型,但略去细节。

定义(definition):提供过编译器一些声明所遗漏的细节。

切记将成员变量声明为 private

The protected label gives derived classes access to the protected members of their constituent base-class objects, but keeps these elements inaccessible to users of the classes. from <Accelerated C++>

protected 和 public 成员变量一样缺乏封装性,如果成员变量被改变,都会有不可预知的大量代码受到破坏。

初始化(initiation):给予对象初值的过程。C++ 规定:对象成员变量的初始化在进入构造函数本体之前,早于赋值。好的初始化方式(效率高)是使用成员初始化列表(member initialization list)。构造函数本体不必有任何动作。

初始化顺序:base class 早于 derived class,class 的成员变量总是以其声明次序被初始化

不同编译单元(源文件)内的 non-local static 对象的初始化顺序属于未定义行为

default 构造函数:构造函数没有参数 or 每个参数都有缺省值。

explicit 构造函数:禁止编译器执行非预期的类型转换(隐式类型转换)。

copy 构造函数:用来以同类型对象初始化自我对象。copy assignment 操作符:用来从另一个同类型对象中拷贝其值到自我对象。

class Widget {
public:
	Widget();                              // default 构造函数
	Widget(const Widget& rhs);             // copy 构造函数
	Widget& operator=(const Widget& rhs);  // copy assignment 操作符
	...
};

Widget w1;          // 调用 default 构造函数
Widget w2(w1);      // 调用 copy 构造函数
w1 = w2;            // 调用 copy assignment 操作符(无新对象被定义)

Widget w3 = w2;     // 调用 copy 构造函数!!!
       // 有新对象被定义(如w3),一定有个构造函数被调用,所以不可能为赋值操作

passed-by-value:意味着“调用 copy 构造函数”。

使用 passed-by-reference-to-const。效率高(避免了对象创建)、避免了对象切割问题。

const char *p;       // 常量数据    
char *const p;       // 常量指针
const char *const p; // 常量指针 & 常量数据

引用与指针

  • 窥视 C++ 编译器源码,你会发现: reference 以指针实现出来
  • reference 不能为 null
  • reference 必须被初始化
  • reference 相较 pointer 效率高,因为使用 reference 之前不需要测试其有效性(使用 pointer 通常得测试它是否为 null)
  • pointer 可被重新赋值,reference 总是指向(代表)它最初获得的对象(“一旦代表了该对象就不能够再改变”)

mutable:去除成员变量的 const 属性。被 mutable 修饰的成员变量可能总是会被修改,即使在 const 成员函数内。

delete:禁止默认拷贝构造函数(copy 构造函数)和赋值操作(copy assignment 操作符)。使用 private 限定符,亦可以达到这个目的(“将成员函数声明为 private 而且故意不实现它们”)。如:

class ios_base {
private:
    ios_base(const ios_base&);
    ios_base& operator=(const ios_base&);
	...
};

如果为空,编译器为你写函数:

  • default 构造函数(在没有声明任何构造函数的情况下)
  • copy 构造函数
  • 析构函数
  • copy assignment 操作符

因此,如果你的类:

class Empty { };

最终是等于写下:

class Empty {
public:
	Empty() { ... }                             //default 构造函数
	Empty(const Empty& rhs) { ... }             //copy 构造函数
	~Empty() { ... }                            //析构函数
	
	Empty& operator=(const Empty& rhs) { ... }  //copy assignment 操作符	
};

  

局部销毁:当 derived class 对象经由一个 base class 指针被删除,而 derived class 的析构函数是 non-virtual 的,(结果未定义) 通常情况是 derived 部分没被销毁(隐含的就是资源泄漏)。解决:为多态基类声明 virtual 析构函数。正确做法如:

class cbase {
public:
	cbase();
	virtual ~cbase(); // !!!
	...
};

cbase *pc = new Derived();
...
delete pc; // correct

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

  • 绝不在析构函数中抛出异常。最好任何情况下都不使用异常。
  • 绝不在构造函数和析构函数中调用 virtual 函数。
  • 令赋值(assignment)操作符返回一个 reference to *this && 避免自我赋值,示例:
Widget& operator=(const Widget& rhs)
{
	if (this == &rhs)  // 防止自我赋值
		return *this;
	...
	return *this;
}
  • 为防止资源泄露,用对象管理,在构造函数中获得资源并在析构函数中释放资源。
  • 好的接口可以防止无效的代码通过编译。
  • 如果成员函数是个 non-virtual 函数,意味着它并不打算在 derived class 中有不同行为,所以绝不该在 derived class 中被重新定义。

类型转换:

  • const_cast<T>(expression):通常被用来将对象的常量性删除(cast away the constness, const->non-const)。
  • dynamic_cast<T>(expression):继承关系的转型,用来执行“安全的向下转型”,执行速度相当慢(从编译器的角度,通过 strcmp 比较、确认不同的类)。
  • reinterpret_cast<T>(expression):低级转型,其转换结果几乎总是与编译平台息息相关,故不具移植性,如 pointer to int 转型为一个 int。
  • static_cast<T>(expression):用来强迫隐式转换,基本上拥有与 C 旧式转型相同的威力与意义,以及相同的限制。

2020.7.10待续。。。

原文地址:https://www.cnblogs.com/rockyching2009/p/13197548.html