Effective C++
-
视 C++ 为一个语言联邦(C、Object-Oriented C++、Template C++、STL)
-
宁可以编译器替换预处理器(尽量以
const
、enum
、inline
替换#define
)编译器可以进行类型检查,避免预处理宏可能导致的类型错误。而且比预处理宏更具有可读性和可维护性,方便调试和错误定位,并且编译器对译器可以对
const
、enum
、inline
等方式定义的常量进行优化,例如可以将常量直接嵌入到代码中,从而提高代码的执行效率。 -
尽可能使用 const
在 C++ 编程中,尽可能使用
const
可以提高代码的可读性、可维护性和安全性,同时也可以带来一些性能优势。以下是一些常见的情况,可以考虑使用const
:- 常量声明:
const double PI = 3.1415926;
- 函数参数:如果函数不会修改参数的值,应该将参数声明为
const
,这可以帮助编译器进行更好的优化,并防止意外修改参数的值。例如:
void printMessage(const std::string& message);
3.迭代器和指针:如果指针或迭代器指向的对象不会被修改,应该将其声明为指向常量的指针或迭代器。例如:
const int* ptr; const_iterator it;
- 成员函数中的成员变量:在成员函数中,如果成员变量不会被修改,应该将其声明为
const
,以提高代码的可读性和安全性。例如:
class MyClass { public: int getValue() const { return value; } private: int value; };
-
确定对象被使用前已先被初始化(构造时赋值(copy 构造函数)比 default 构造后赋值(copy assignment)效率高)
"default 构造后赋值(copy assignment)"指的是使用默认构造函数创建对象后,再通过赋值运算符(copy assignment operator)将另一个对象的值赋给该对象。
class MyClass { public: int value; // 默认构造函数 MyClass() : value(0) { } // 赋值运算符(copy assignment operator) MyClass& operator=(const MyClass& other) { if (this != &other) { // 检查是否是自我赋值 value = other.value; } return *this; } }; int main() { MyClass obj1; // 使用默认构造函数创建对象 obj1 MyClass obj2; // 使用默认构造函数创建对象 obj2 obj2 = obj1; // 使用赋值运算符将 obj1 的值赋给 obj2 return 0; }
-
了解 C++ 默默编写并调用哪些函数(编译器暗自为 class 创建 default 构造函数、copy 构造函数、copy assignment 操作符、析构函数)
-
若不想使用编译器自动生成的函数,就应该明确拒绝(将不想使用的成员函数声明为 private,并且不予实现)
-
为多态基类声明 virtual 析构函数(如果 class 带有任何 virtual 函数,它就应该拥有一个 virtual 析构函数)
-
别让异常逃离析构函数(析构函数应该吞下不传播异常,或者结束程序,而不是吐出异常;如果要处理异常应该在非析构的普通函数处理)
-
绝不在构造和析构过程中调用 virtual 函数(因为这类调用从不下降至 derived class)
-
令
operator=
返回一个reference to *this
(用于连锁赋值)这句话的意思是在重载赋值运算符(
operator=
)时,让它返回一个对当前对象的引用,通常是*this
。这样做的目的是为了支持连锁赋值操作,即可以通过连续地对同一对象进行赋值操作。考虑下面的示例:
class MyClass { public: int value; // 重载赋值运算符 MyClass& operator=(const MyClass& other) { if (this != &other) { value = other.value; } return *this; // 返回对当前对象的引用 } }; int main() { MyClass obj1, obj2, obj3; // 连锁赋值操作 obj1 = obj2 = obj3; return 0; }
在这个例子中,
operator=
被重载为返回对当前对象的引用*this
。因此,连锁赋值obj1 = obj2 = obj3
的执行顺序是从右向左,首先obj2 = obj3
被执行,然后返回对obj2
的引用,接着obj1 = obj2
被执行,并返回对obj1
的引用。这样就实现了连锁赋值操作。如果
operator=
没有返回引用,则无法进行连锁赋值操作,因为每次赋值操作都会返回一个新的对象,而不是对原始对象的引用。因此,重载赋值运算符时通常会让它返回一个对当前对象的引用,以支持连锁赋值操作。 -
在
operator=
中处理 “自我赋值”// 重载赋值运算符 MyClass& operator=(const MyClass& other) { // 检查自我赋值 if (this != &other) { // 删除旧资源 delete data; // 分配新资源并复制数据 data = new int(*other.data); } return *this; }
-
赋值对象时应确保复制 “对象内的所有成员变量” 及 “所有 base class 成分”(调用基类复制构造函数)
#include <iostream> // 基类 class Base { public: int baseValue; // 基类构造函数 Base(int value) : baseValue(value) { } // 基类复制构造函数 Base(const Base& other) : baseValue(other.baseValue) { } // 基类赋值运算符重载 Base& operator=(const Base& other) { if (this != &other) { baseValue = other.baseValue; } return *this; } }; // 派生类 class Derived : public Base { public: int derivedValue; // 派生类构造函数 Derived(int base, int derived) : Base(base), derivedValue(derived) { } // 派生类复制构造函数 Derived(const Derived& other) : Base(other), derivedValue(other.derivedValue) { } // 派生类赋值运算符重载 Derived& operator=(const Derived& other) { if (this != &other) { Base::operator=(other); // 调用基类赋值运算符重载 derivedValue = other.