文章目录
一、C++
1、锁操作
模块内部加锁时,不要锁住对外的回调,避免外部回调再反调模块接口,导致死锁。切记:锁是针对多线程操作同一个资源的保护,锁只保护资源,不针对操作流程,锁的区间粒度要小,尽量避免嵌套锁,循环锁
。
#include <function>
#include <mutex>
#include <thread>
class Test {
public:
Test() : m_num(0) {}
void increment(std::function<void(int)> callback) {
/* 错误 */
//std::lock_guard<std::mutex> lock(m_mutex);
//++m_num;
//if (num < 9999999) {
// callback(m_num);
//}
/* 正确 */
int num = 0;
{
std::lock_guard<std::mutex> lock(m_mutex);
++m_num;
num = m_num;
}
if (num < 9999999) {
callback(num);
}
}
private:
std::mutex m_mutex;
int m_num;
};
Test s_test;
void func(int num) {
printf("===== num: %d\n", num);
s_test.increment(func);
}
void threadHandler() {
s_test.increment(func);
}
int main() {
for (int i = 0; i < 2; ++i) {
std::thread th(threadHandler);
th.detach();
}
getchar();
return 0;
}
2、类成员变量初始化
初始化一个类的成员变量,在定义时初始化,还是在构造函数中初始化?貌似早期的C++版本不允许在定义时初始化,但是C++11标准及以后允许。成员变量初始化的顺序为:
- 先进行定义时初始化。
- 然后进行初始化列表初始化。
- 最后进行构造函数初始化。
#include <iostream>
class A {
public:
A() {}
A(int n) : num(n) {}
A(int n, bool b) : num(4) {
num = n;
}
public:
int num = 1;
};
int main() {
A a1;
A a2(3);
A a3(5, true);
std::cout << "a1.num = " << a1.num << std::endl;
std::cout << "a2.num = " << a2.num << std::endl;
std::cout << "a3.num = " << a3.num << std::endl;
system("pause");
return 0;
}
结果可以看出:初始化列表初始化的变量值会覆盖掉定义时初始化的值,而构造函数中初始化的值又会覆盖掉初始化列表的值。
有人说这种初始化方式破坏了类的抽象性。在一定程度上是的,但是却能带来很大便捷。好比一台机器能够生产某种零件,零件的各个尺寸可以在一定程度任意改变,但是我们并不是每次生产这种零件都需要一个一个地给定每一个参数,然后再生产,机器会自己带有一套“默认”的零件尺寸参数,这样新手可以直接用默认参数生产,除非你知道每一个参数都是干啥的,否则没必要更改。那么也可以在构造函数中给出这一套“默认参数”啊,是的,但是假如有10个构造函数,那么每一个都要复制一次所有成员变量的默认参数吗?显然在做重复的无用工作,那么为什么不直接在定义的时候就初始化呢。
同时,可以参考一下Bjarne Stroustrup近期撰写的《CPP Core Guidelines》,其中第C.48条就提到了:
C.48: Prefer in-class initializers to member initializers in constructors for constant initializers
结论就是优先考虑放到in-class里面,也就是成员声明的时候就进行初始化。
3、利用宏自动生成变量名
#define _VARIABLE_NAME_CONNECTION_(var1, var2) var1##var2
#define _VARIABLE_NAME_GENERATE_(var1, var2) _VARIABLE_NAME_CONNECTION_(var1, var2)
#define AUTO_VARIABLE(var) _VARIABLE_NAME_GENERATE_(var, __LINE__)
/* 示例1 */
int AUTO_VARIABLE(num) = __LINE__; /* 等价于: int num行号 = 行号; 假设行号为13,则变量名为: num13,本行等价于:int num13 = 13; */
4、慎用__FILE__
、__FUNCTION__
宏
- 代码上不允许使用
__FUNCTION__
,代码中使用__FUNCTION__
会把函数名生成到动态库的静态符号中,别人可以方便地通过命令导出,可能影响代码安全。 - 同样的道理,
__FILE__
更加不允许使用,这个宏会把编译期的文件路径生成到动态库中。
5、尽量避免使用宏#define
6、优先使用lambda
而非std::bind
lambda
表达式具有更强的可读性,表达力更好。std::bind
函数绑定重载函数时会遇到二义性问题。由于函数名只是个名字,不带形参类型等其他附加信息,所以无法知道被绑定的是哪个重载版本的函数。lambda
表达式可能生成比使用std::bind
运行效率更高的代码。编译器可能对函数名做inline
函数调用,而不太可能对函数指针做这种优化。如setAlarm
函数在lambda
中的调用,可以被内联。但是std::bind
绑定的是setAlarm
函数指针而不是函数体本身,所以无法被内联。lambda
的传参方式(按值还是按引用)比std::bind
函数更直观。lambda
表达式的传参方式只需看其声明就可知。但使用std::bind
需要牢记其工作原理,即std::bind
在绑定时是按值传递实参的,绑定对象在调用时是按引用传递实参的。- 自C++14以后,
std::bind
己经彻底失败用武之地,可以完全被lambda
替代。