C++常见避坑指南

C++ 从入门到放弃?本文主要总结了在C++开发或review过程中常见易出错点做了归纳总结 , 希望借此能增进大家对C++的了解,减少编程出错,提升工作效率,也可以作为C++开发的避坑攻略 。
空指针调用成员函数会crash??当调用一个空指针所指向的类的成员函数时 , 大多数人的反应都是程序会crash 。空指针并不指向任何有效的内存地址,所以在调用成员函数时会尝试访问一个不存在的内存地址 , 从而导致程序崩溃 。
事实上有点出乎意料,先来看段代码:
class MyClass {public:  static void Test_Func1() {    cout << "Handle Test_Func1!" << endl;  }  void Test_Func2() {    cout << "Handle Test_Func2!" << endl;  }  void Test_Func3() {    cout << "Handle Test_Func3! value:" << value << endl;  }  virtual void Test_Func4() {    cout << "Handle Test_Func4!" << endl;  }  int value = 0;};int mAIn() {  MyClass* ptr = nullptr;  ptr->Test_Func1(); // ok, print Handle Test_Func1!  ptr->Test_Func2(); // ok, print Handle Test_Func2!  ptr->Test_Func3(); // crash  ptr->Test_Func4(); // crash  return 0;}上面例子中,空指针对Test_Func1和Test_Func2的调用正常,对Test_Func3和Test_Func4的调用会crash 。可能很多人反应都会crash,实际上并没有,这是为啥?
类的成员函数并不与具体对象绑定 , 所有的对象共用同一份成员函数体,当程序被编译后,成员函数的地址即已确定,这份共有的成员函数体之所以能够把不同对象的数据区分开来,靠的是隐式传递给成员函数的this指针 , 成员函数中对成员变量的访问都是转化成"this->数据成员"的方式 。因此,从这一角度说,成员函数与普通函数一样,只是多了this指针 。而类的静态成员函数只能访问静态成员变量,不能访问非静态成员变量,所以静态成员函数不需要this指针作为隐式参数 。
因此,Test_Func1是静态成员函数,不需要this指针,所以即使ptr是空指针,也不影响对Test_Fun1的正常调用 。Test_Fun2虽然需要传递隐式指针 , 但是函数体中并没有使用到这个隐式指针,所以ptr为空也不影响对Test_Fun2的正常调用 。Test_Fun3就不一样了 , 因为函数中使用到了非静态的成员变量,对num的调用被转化成this->num,也就是ptr->num,而ptr是空指针 , 因此会crash 。Test_Fun4是虚函数,有虚函数的类会有一个成员变量,即虚表指针,当调用虚函数时,会使用虚表指针,对虚表指针的使用也是通过隐式指针使用的,因此Test_Fun4的调用也会crash 。
同理,以下std::shared_ptr的调用也是如此,日常开发需要注意,记得加上判空 。
std::shared_ptr<UrlHandler> url_handler;...if(url_handler->IsUrlNeedHandle(data)) {  url_handler->HandleUrl(param);}字符串相关字符串查找对字符串进行处理是一个很常见的业务场景,其中字符串查找也是非常常见的,但是用的不好也是会存在各种坑 。常见的字符串查找方法有:std::string::find、std::string::find_first_of、std::string::find_first_not_of、std::string::find_last_of,各位C++ Engineer都能熟练使用了吗?先来段代码瞧瞧:
bool IsBlacklistDllFromSrv(const std::string& dll_name) {    try {        std::string target_str = dll_name; std::transform(target_str.begin(), target_str.end(), target_str.begin(), ::tolower);        if (dll_blacklist_from_srv.find(target_str) != std::string::npos) {            return true;        }    }    catch (...) {    }    return false;}上面这段代码,看下来没啥问题的样子 。但是仔细看下来,就会发现字符串比对这里逻辑不够严谨,存在很大的漏洞 。std::string::find只是用来在字符串中查找指定的子字符串 , 只要包含该子串就符合,如果dll_blacklist_from_srv = "abcd.dll;hhhh.dll;test.dll" 是这样的字符串 , 传入d.dll、hh.dll、dll;test.dll也会命中逻辑,明显是不太符合预期的 。


推荐阅读