整理了下在C++工程代码中遇到的技巧与建议。
0x00 巧用宏定义。
经常看见程序员用 enum 值,打印调试信息的时候又想打印数字对应的字符意思。见过有人写这样的代码 if(today == MONDAY) return "MONDAY"; 一般错误代码会有很多种,应该选用 switch-case 而不是 if-else。因为 if-else 最坏比较N次,switch-case 最坏是lgN次。这里用上宏,代码简介明了。而且也去掉了查找,直接索引到对应的字符串。
enum Day
{
SUNDAY = 0,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY,
COUNT
};
const char* toString(Day day)
{
#define CODE(code) array[static_cast<uint32_t>(code)] = #code
const char* array[COUNT];
CODE(SUNDAY);
CODE(MONDAY);
// ...
#undef CODE
assert(0 <= day && day < COUNT);
return array[day];
}
接着有人说,数组每次运行会初始化一次啊。是的,可以用 static 变量达到仅初始化一次,生成的汇编代码里,会设置一个bit位,用来检查是否已初始化,而作相应的跳转。
当然,如果想执行一段代码或一个函数一次。即让函数有 static 变量的效果,可不是也加 static 关键字了(static 函数表示作用域仅在该文件可见),可以这么写:
void do_something()
{
std::cout<< "hello world\n";
}
bool run_once()
{
do_something();
return true;
}
int main()
{
static bool unused = run_once();
return 0;
}
这样稍微包装一下,就可以用了。利用 C++11 的 lambda 函数,可以不用额外写函数啦!
static bool unused = []() -> bool { do_something(); return true; }();
最简单的 lambda 函数申明 [](){}; 调用该函数就是 [](){}();,怎么样,新奇吧,好玩吧?
0x01 效率提升之用临时寄存器变量减少成员变量读写次数。
在Gameloft做游戏开发的时候,经常要考虑如何提升游戏的性能。iPhone iPad的硬件水平非常好,Android 的硬件参差不齐,所以游戏从 iOS 移植到 Android 需要考虑 Android 的感受。讲一下我在游戏中遇到的一个问题。类中有一个数组 values,我们对它的计算结果 mTotal 作了缓存,这样不用每帧都计算。
for(int i = 0; i < count; ++i) mTotal += values[i]; //////////////////////////////// register int64_t total = 0; for (int i = 0; i < count; ++i) total += values[i]; mTotal = total;
分割线上下几行代码实现的功能是一样的,但是下面的速度更快些。下面用了一个栈临时变量(用了 register,也可能分配到一个寄存器),不用在每次循环中都读写成员变量,而是计算完后一次写入。可能有些人还是不明白,可以尝试自己反编译成汇编代码查看分析——不管读还是写,都经过先找到对象,再找到成员变量。
0x02 模拟 Java 里 String 的 startWith 方法。
#include <string> std::string haystack("malicious"); std::string needle("mali");
bool startWith(const std::string& haystack, const std::string& needle) { return haystack.compare(0, needle.length(), needle) == 0; }
C++ 里没有提供 startWith 方法,所以 Java 程序员转 C++ 的时候,处理字符串的时候,就很想找到这个函数。其他转语言的开发者都会有类似的经历。上面用 haystack, needle 是套用 strstr 的参数名,很形象的比喻。man strstr 后,char *strstr(const char *haystack, const char *needle);。另外提一下,取 std::string 的长度,用 length() 或 size() 都可以,但最好用 length(),因为 size() 是 STL 里惯用的概念,以示区别。
0x03 scanf、printf 函数
读取一个字符串,遇到逗号停止,可以这么写:scanf("%[^,]\n", str);。但如果是多个字符串,忽略掉之前的字符,怎么写好呢?例如我想获取"Taylor Swift"的姓,一般的就这么写了,scanf("%s %s", temp, last_name); 这样占用了一个临时变量,改成 scanf("%s ", last_name); scanf("%s ", last_name); 的话,需要两次调用 scanf 函数,最佳的答案是 scanf("%*s %s", last_name); 。是这样的,如果你想忽略某个输入,可以在%后用*。
写数据前,不能确定缓冲区的大小,该如何写?printf 族函数,大家都知道是用来打印的,很少有人能说出它有返回值,并且返回值是写入字符的个数。这里,返回值就派上用场了。
Calling std::snprintf
with zero buf_size
and null pointer for buffer
is useful to determine the necessary buffer size to contain the output:
const char *fmt = "sqrt(2) = %f";
int sz = std::snprintf(nullptr, 0, fmt, std::sqrt(2));
std::vector<char> buf(sz + 1); // note +1 for null terminator
std::snprintf(&buf[0], buf.size(), fmt, std::sqrt(2));
Visual C++ 先前没有提供 snprintf 函数,取而代之的是 _snprintf, _snprintf_s。长得像,但是完成的功能与C语言标准有些出入。直到 Visual Studio 2015 才加进来。_snprintf 写时溢出的时候不会写结束的\0字符,_snprintf_s 改善了安全性,但是在溢出的时候返回 -1,而不是标准要求的写入的字符的长度。
#ifndef COMPILER_H_ #define COMPILER_H_ #include "stdio.h" #include "stdarg.h" /* Microsoft has finally implemented snprintf in VS2015 (_MSC_VER == 1900). Releases prior to Visual Studio 2015 didn‘t have a conformant implementation. There are instead non-standard extensions such as _snprintf() (which doesn‘t write null-terminator on overflow) and _snprintf_s() (which can enforce null-termination, but returns -1 on overflow instead of the number of characters that would have been written). */ #if defined(_MSC_VER) && _MSC_VER < 1900 #define snprintf c99_snprintf #define vsnprintf c99_vsnprintf inline int c99_vsnprintf(char *outBuf, size_t size, const char *format, va_list ap) { int count = -1; if (size != 0) count = _vsnprintf_s(outBuf, size, _TRUNCATE, format, ap); if (count == -1) count = _vscprintf(format, ap); return count; } inline int c99_snprintf(char *outBuf, size_t size, const char *format, ...) { int count; va_list ap; va_start(ap, format); count = c99_vsnprintf(outBuf, size, format, ap); va_end(ap); return count; } #endif #endif /* COMPILER_H_ */
0x04 类型转换。
在Gameloft的日子里,每个月都会定期代码检查(Code Review),结果会通报给各位。很多人不情愿写 C++ 的类型转换,因为它有点长,没C语言的括号来的便捷。导致后来的我养成习惯,写C++代码时候,一律正确使用 C++的转换,而且现在的编译器都会有补全提示功能,不需要敲完整个关键字了。而且在游戏里,避免使用C++ 的 RTTI 和 exception 功能,RTTI 开销大,C++ 以前的异常处理简直就是一鸡肋。这里,矛盾就出现了,dynamic_cast 按照前者(C++ cast)应该使用,按照后者(用了RTTI)是不应该使用的。于是就有了下面的代码,debug 版调试时用来检测正确的类型,在 release 版本强转。很多对象继承于 Entity,于是 Creature 类,就用了所谓的 CREATURE_CAST 宏,其实就是 #define CREATURE_CAST(entity) downcast<Creature*>(entity)
template <typename To, typename From> To downcast(From p) { #ifdef DEBUG To to = dynamic_cast<To>(p); assert(to != nullptr); return to; #else return static_cast<To>(p); #endif }
0x05 跳转新玩法。
对于 int a, b 两个值,如果都满足的话,执行某语句。大概很多人都会这么写: if(a >0 && b > 0) do_something();
我在看代码的时候,看到了一种很赞的写法,忘记出处了。写法是: if((a|b) > 0) do_something();
相比而言,少了一条跳转指令。你可以感受一下。
想到这,还要提醒一下,不要在函数里写 if(isGood) return true; else return false; 这样的代码了,直接写 return isGood 就好了。
0x06 char* 与 std::string 之间的迁移。
std::vector<Creature> creatures; for(const Creature creature: creatures) creature.roar();
乍一看,没什么啊。我提醒了他,这里应该用引用,for(const Creature& creature: creatures),可以避免调用复制构造函数的开销,就跟函数的传值与传引用一样。
template<class Iter, class T> Iter binary_find(Iter begin, Iter end, T value) { // Finds the lower bound in at most log(last - first) + 1 comparisons Iter it = std::lower_bound(begin, end, value); if (it != end && !(value < *it)) return i; // found it else return end; // not found }
STL 取 find 名的函数是返回迭代器的。
注意,这里是模板函数,不要用 *it == value,要用 !(value < *i)。原因是 std::lower_bound 用的是 < ,即严格的弱序关系。这里的类型 T 可以没有相等==的判断。相等(equality)与等价(equivalence)是不能搞混淆的。可以查看 Scott Meyers 的书 Effective STL 的 19 条,有关 equality 与 equivalence 的区别。
我在上面也栽倒过。之前重载关于 tuple 的比较函数,被我写成了
class IndexCompare { public: bool operator()(const vec3i& a, const vec3i& b) const { return a.i < b.i || a.j < b.j || a.k < b.k; } };
而正确的应该是这样的,采用"skip while equal, then compare"的策略。std::vector 有重载 operator < 操作符,也可以直接拿来用。
class IndexCompare { public: bool operator()(const vec3i& a, const vec3i& b) const { // Operator < and strict weak ordering if(a.i != b.i) return a.i < b.i; if(a.j != b.j) return a.j < b.j; if(a.k != b.k) return a.k < b.k; return false; } };
原文:http://www.cnblogs.com/Martinium/p/cpp_tips_and_tricks.html