编程中踩坑及经验总结

一、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替代。
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值