Effective C++

[toc]

导读

  • 声明式:告诉编译器某个东西的名称类型,但是略去细节。
1
2
3
templete<typename T>
class Randolfluo;
std::string Print(std::string string);
  • 定义式:提供编译器对象、函数和模板等的实际代码本体。

  • 初始化:给予对象值的过程。

  • copy构造copy赋值:通过是否有新对象被定义区分。

Accustoming youself to C++

01. View C++ as a federation of languages

C++是一个多重范式语言:

  • 过程形式(procedural):通过函数调用和控制流语句(如if语句和循环)来实现。
  • 面向对象形式(object-oriented):支持封装、继承和多态等面向对象的特性。
  • 函数形式(functional)
  • 泛型形式(generic):C++通过模板支持泛型编程。模板允许你编写可以处理各种数据类型的通用代码。标准模板库(STL)中的容器和算法都是泛型的,可以与各种数据类型一起使用。
  • 元编程形式(metaprogramming):C++提供了元编程的功能,其中最常见的是模板元编程。通过模板元编程,你可以在编译时生成代码,实现诸如计算、类型推导、代码生成等功能。

02. Prefer consts,enums,and inlines to #define

预处理

  • 由于预处理符号#会在函数编译前进行复制和替换,如果发生错误,我们得到的信息将是1234而不是Randolfluo,于是你将为追踪他而浪费时间。

  • 正确的方法是以常量替换宏。

  • #define不注重作用域,因此不具封装性。

1
2
#define Randolfluo =1234		
const int Randolfluo = 1234
  • 宏看起来像函数,但是不会招致函数调用(function call)带来的额外开销。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include<iostream>
#define Macro_Print(a,b) std::cout << (a) + (b) << std::endl; //错误方式,相较于函数调用效率更高,但是调用时可能产生错误输出。

template<typename T>
inline void Inline_Print(const T& a, const T& b) //正确方式,性能相同,但是具有类型安全,pass by reference-to-const
{
std::cout << a + b << std::endl;
}
int main()
{
int a = 0, b = 0;

Macro_Print(a, b);
Inline_Print(a, b);
}

类专属常量

  • 静态成员变量的定义通常需要在类外部进行,以便为其分配存储空间。这是因为在类声明中只是声明了静态成员变量的存在,而没有分配内存。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

class MyClass {
public:
static const int MY_CONSTANT = 10; // 类专属常量声明
};

// 类专属常量定义和初始化
const int MyClass::MY_CONSTANT;

int main() {
std::cout << "My class constant: " << MyClass::MY_CONSTANT << std::endl;
return 0;
}

请记住

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

03. Use const whenever possible

  • 令函数返回一个常量值,往往可以降低因错误而造成的意外。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
class Num
{
private:
int num;
public:
const Num operator+ (const Num& other) const
{
Num result;
result.num = num + other.num;
return result;
}
};


int main()
{
Num a, b, c;
c = a + b;
if( a + b = c ) //少写一个=号,如果不加入const,则此操作编译器将不报错
}

const成员函数

  • 将const实施于成员函数的目的,是为了确认该成员函数可作用于const对象。
  • 通过pass by reference-to-const 传递对象,提高程序效率。

bitwise constness & logical constness

  • 请看书

请记住

  • 将某些东西声明为const可帮助编译器侦测出错误用法。const可被施加于任何作用域内的对象、函数参数、函数返回类型、成员函数本体。
  • 编译器强制实施bitwise constness,但你编写程序时应该使用“概念上的常量性”( conceptual constness)。
  • 当const和non-const成员函数有着实质等价的实现时,令non-const版本调用const版本可避免代码重复。

04. Make sure that objects are initialized before ther’re used.

  • 永远在使用对象前先将它初始化。

member intialization list

  • 如果成员变量是constreference,就要赋初值,使用成员初始化列表
  • 类成员的初始化顺序不是按照初始化列表的顺序来的,而是按照类成员的声明顺序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

class Entity
{
public:
Entity()
:x(12) //member intialization list
{
std::cout << x << std::endl;
}
private:
const int x = 10; //default member intializer
};

int main()
{
Entity e;

}
//output 12

local static & non-local static

  • local static对象即函数内部的 static 变量,适用于需要在函数调用间保持状态的情况,可以防止变量的多次初始化,且仅在声明的函数内部可见。
  • non-local static即函数外部的 static 变量,适用于在文件内部共享状态的情况,可以防止变量被其他文件访问,且仅在声明的文件内部可见。

  • Singleton模式:将每个non-local static对象搬到自己的专属函数内(该对象在此函数内被声明为static[即local static对象])。这些函数返回一个reference 指向它所含的对象。然后用户调用这些函数,而不直接指涉这些对象。换句话说,non-local static 对象被local static 对象替换了。

//TODO为什么默认构造函数比有初值的构造函数效率低

请记住

  • 为内置型对象进行手工初始化,因为C++不保证初始化它们。
  • 构造函数最好使用成员初值列( member initialization list),而不要在构造函数本体内使用赋值操作(asignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以local static 对象替换non-local static对象。