本文将介绍一些常见的 C++ 代码优化技巧,本文为该系列的第一章,未来不定期更新。

使用前置递增(递减)运算符

可能很多从 C 语言开始学习的朋友会对此感到困惑,觉得这两者在适用的情况下可以任意选择,如 for 循环语句写作 for (int i = 0; i < n; i++) 。这样做当然没错,但会造成一定程度上的资源浪费,因为后置递增运算符需要先将原本的变量值保存下来,再对其进行递增操作,而在此 for 循环中,我们并不需要使用原本的 i 值。并且 ++i 的写法相对来说更符合我们的意愿。

当然对于基本数据类型来讲,在经过编译器的优化之后,两者的效率可能并没有什么差别,但对于 STL 中的模板容器,或是自定义数据结构的迭代器来讲,前置运算符的效率显然要更高,因为单个对象所占的内存空间更大,使得拷贝暂存的开销也越大。

因此,除非必要情况,应该尽可能使用前置递增(递减)运算符。

使用引用传递

引用类型变量并不是一个对象,它只是一个已存在对象的别名,因此在作为变量传递时不会经过拷贝构造的过程,能够显著地提升效率,以下为值传递的情况。

struct myStruct
{
	int x, y, z;
	myStruct(int x, int y, int z) : x(x), y(y), z(z) {}
	myStruct (const myStruct& ms) : x(ms.x), y(ms.y), z(ms.z) {
		std::cout << "copy constructor is called!" << std::endl;
	}
};

void printMs(myStruct ms) { 
	std::cout << ms.x << " " << ms.y << " " << ms.z << std::endl;
}

int main()
{
	myStruct ms(1, 2, 3);
	printMs(ms); // 输出"copy constructor is called!"
	std::cin.get();
}

事实上,我们只是想通过调用该函数输出结构体对象的成员变量值,因此并不需要对原对象进行拷贝,这将会带来不必要的资源浪费,因此函数的参数应该使用引用传递,此外,由于函数只需要进行输出操作,而不涉及对象修改,因此参数可以加上 const 限定符,将原函数修改为 void printMs(const myStruct& ms) .

我们知道 C++ 11 引入了一种新的 for 循环语句格式:for (type element : array),即遍历 array 中的所有元素,因此每次循环都将执行一次 element = array[i]; ++i; ,因此如果 element 不是引用类型,则每次循环都将进行一次拷贝,这同样会造成资源浪费。正确的写法应该是 for (type& element : array),如无需更改对象值,则最好加上 const 限定符。

此外C++ 11 还加入了自动类型推导 auto 关键字,虽然该关键字不宜滥用,但在基于范围的 for 循环中十分适用,尤其在面对一些较复杂的类型名时,可以使得代码更加简洁:for (auto& element : array).

使用 emplace() 替代 push()

emplace() 函数同样是 C++ 11 引入的新特性,用以替代原来的 push()

以往我们希望向 STL 模板容器中添加元素时,通常会先创建一个元素对象,再调用 push() 将其添加进容器中(此处的 push() 指的是 push_back(), push_front(), insert() 等一系列函数)。这样相当于需要先构造原对象,再拷贝该对象。但如果我们并没有事先创建这个对象,而是希望添加具有特定参数值的对象,那这样做无疑会带来不必要的性能开销,那有没有一种方法能够让我们直接在容器内构造对象呢?emplace() 就具备这样的功能,以下示例对比了 emplace_back() 和 push_back() 的使用方法,并通过重写构造函数和拷贝构造函数来追踪构造和拷贝的调用情况。

struct myStruct
{
	int x, y, z;
	myStruct(int x, int y, int z) : x(x), y(y), z(z) {
		std::cout << "constructor is called!" << std::endl;
	}
	myStruct (const myStruct& ms) : x(ms.x), y(ms.y), z(ms.z) {
		std::cout << "copy constructor is called!" << std::endl;
	}
};

int main()
{
	std::vector<myStruct> msVec;
#if 1
	myStruct ms(1, 2, 3);
	msVec.push_back(ms);
    // 输出
    // constructor is called!
	// copy constructor is called!
#endif

#if 0
	msVec.emplace_back(1, 2, 3);
#endif
	std::cin.get();
}

可见,push_back() 的原理如上所述,调用了一次构造函数和一次拷贝构造函数。注意,此处的 #if <expression> <block> #endif 是预处理操作,当 #if 后的 <expression> 为真,则执行 <block> 代码,否则不执行,因此该操作在这里用作注释。

当我们执行 emplace_back() 时,结果如下:

int main()
{
	std::vector<myStruct> msVec;
#if 0
	myStruct ms(1, 2, 3);
	msVec.push_back(ms);
#endif

#if 1
	msVec.emplace_back(1, 2, 3);
    // 输出 constructor is called!
#endif
	std::cin.get();
}

当我们希望原地构造时,emplace_back() 参数与元素对象类型的构造函数相同。同样的我们也可以传入一个已经存在的对象,这样效果和 push_back() 相同。