SamJ's blog

EffectiveC艹笔记(一)

Word count: 1kReading time: 4 min
2019/10/24 Share

条款一:将C++视为一个语言联邦(将C++划分为不同的次级语言)

将C++视为四个次语言,进入不同的次语言领域有不同的标准:

  • C:内置数据类型,预处理,数组,指针
  • Object-Oriented C++:类,封装,继承,多态,虚函数
  • Template C++:模板元编程
  • STL:STL组件

条款二:尽量以const,enums, inline 替换#define

  • 使用const或者enums替换#define,使用template的inline函数代替宏(#define)

    • 用const替换#define定义的宏便于调试,可以再符号表里找到定义的常量
    • 定义常量指针要用两个const: const char* const authorName = “Scott Meyers”;(这里说常用string定义authorName如:const std::string authorName = “Scott Meyers”)
    • 定义class的专属常量时,要写成static,确保该常量只有一个实体
    • enumhack,用一个enum代替int常量,可以防止别人获得指向该常量的指针

    用inline代替define:

    1
    2
    3
    4
    5
    6
    7
    #define CALL_WITH_MAX(a, b) f((a) > (b) ? (a) : (b))

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

    相对于宏,这种写法不容易出错。

条款三:尽可能使用const

要尽可能使用const,对于需要约束的变量一定要进行约束。

1
2
char* const p = str;		//指针是常量,指针指向数据不是
const char* p2 = str; //指针不是常量,指针指向数据是

若被指物是常量,const在星号前,指针自身是常量,const在星号后。

对于迭代器,const vector<int>::iterator iter = vec.begin() 作用类似于T * const

vector<int>::const_iterator iter = vec.begin()作用类似于const T*

bitwise constness:成员函数在不更改对象的任何成员变量时才能称为const

1
2
3
4
5
6
7
8
9
//不具备const性质但可以通过bitwise constness检查的例子:
class CTextBlock{
public:
char& operator[](std::size_t position) const
{return pText[position];}
private:
char* pText;
};
//该代码可以通过bitwise constness测试,但可以通过[]修改pText指向的字符串的值

logical constness:在客户端看来是const的,也就是说实现上可能会改变数据成员的值,但是用户却感觉不到(mutable关键字)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class CTextBlock{
public
std::size_t length() const;
private:
char* pText;
mutable std::size_t textLength;
mutable bool lengthIsValid;
};
std::size_t CTextBlock::length() const
{
if(!lengthIsValid)
{
textLength = std::strlen(pText); //mutable保证可以再const成员函数中修改
lengthIsValid = true;
}
return textLength
}

const成员函数:

对于实现相同功能的成员函数,对他们的const 和非const版本,用非const版本调用const版本避免代码重复。

条款四:确定对象使用前已经被初始化

  • 对于内置类型,必须在使用之前进行初始化
  • 当使用构造函数时,使用初始化列表来定义构造函数。
  • 对于静态对象,当多个non-local static对象处于不同的源文件内,且有初始化顺序的约束,即在使用一个对象时必须保证另一个对象已经被初始化,这时要使用单例模式。

条款五:了解C++会默认调用的函数

C++会自动提供默认的构造函数,拷贝构造函数,析构函数,和赋值运算符,且均为public inline 。

1
2
3
4
5
6
7
8
9
10
11
12
//如果你写了一个空类
class Empty{ };
//相当于你写了下面这样一个类
class Empty
{
public:
Empty() {...}
Empty(const Empty &rhs) {...}
~Empty(){...}

Empty& operator=(const Empty & rhs) {...}
};

对于拷贝构造函数和赋值运算符,默认的版本只单纯将来源对象的每一个non-static成员拷贝到目标对象,注意,若对成员的拷贝操作不合法时,例如当成员是引用类型的时候(引用类型不能进行拷贝),编译器会报错,此时调用默认的拷贝构造函数和赋值运算符是很危险的。

当基类将赋值运算符设为private,其子类就不会提供默认的拷贝运算符(原因是基类无法拷贝)。

  • 使用自定义类型时要注意是否使用编译器提供的默认构造函数,析构函数,拷贝构造函数,赋值运算符。
CATALOG
  1. 1. 条款一:将C++视为一个语言联邦(将C++划分为不同的次级语言)
  2. 2. 条款二:尽量以const,enums, inline 替换#define
  3. 3. 条款三:尽可能使用const
  4. 4. 条款四:确定对象使用前已经被初始化
  5. 5. 条款五:了解C++会默认调用的函数