各种c++技巧
- 说明
- gcc编译c++出现接口不兼容的现象看这里
- gcc4和gcc5的编译出来的二进制接口不匹配
- 包装迭代器
- 内存对齐
- Magic Static
- 如何在不修改代码的情况下访问,私有成员变量
- 整形的类型头文件
- SFINAE
- 如何判断一个类有没有某个成员函数 (模板元版)
- 如何判断一个类有没有某个成员函数 (函数版)
- 采用更丰富的std::string库
- dlerror
- null
- 在保证lib的接口不变的情况下,调用者能在编译期检查出abi的不匹配
- 统计每一行代码的执行频率
- 获取时间
- raw string
- User-defined literals 用户自定义后缀
- 改变字体颜色
- 编译器参数
- 枚举
- extern
- 子类中调用父类模板函数
- 函数默认参数,只能写一次
- override
- lambda
- functional-try块
- 命令规则
 
 
- define MAX(a,b) 宏全部大写,并且#define直接左对齐
说明
c++的各种技巧
c++进阶推荐 《Imperfect c++ 中文版》 《c++模板元编程》
google c++ 命名规范
cppreference
gcc编译c++出现接口不兼容的现象看这里
_GLIBCXX_USE_CXX11_ABI宏的作用
参考GCC提供的手册
在 GCC 5.1 版本中,libstdc++ 引入了一个新特性,其中包括 std::string 和 std::list 的新实现。为了符合 C++11 标准,这些更改是必要的,该标准禁止 Copy-On-Write 字符串并要求列表跟踪其大小。
这样虽然符合了c++11的标注,旧版就无法兼容了。为了解决这个问题,对于旧版而言,GCC5.1添加了__cxx11命名空间,GCC5.1或者说c++11规范下的string和list,实际上是std::__cxx11::string和std::__cxx11::list,所以我们一般的using namespace std就会变成形如using namespace std::__cxx11的样子。也就是说,有旧版(c++03规范)的libstdc++.so,和新版(c++11规范)的libstdc++.so两个库同时存在。
为了避免两个库到底选择哪一个的麻烦,GCC5.1就引入了-D_GLIBCXX_USE_CXX11_ABI来控制编译器到底链接哪一个libstdc++.so,
-D_GLIBCXX_USE_CXX11_ABI=0 链接旧版库
-D_GLIBCXX_USE_CXX11_ABI=1 链接新版库
如果在CMakeLists里设置
add_definitions(-D _GLIBCXX_USE_CXX11_ABI=0)
Reference:
[1]. https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_dual_abi.html
gcc4和gcc5的编译出来的二进制接口不匹配
这个错误是在我把gcc4.8编的工程迁移到ubuntu16.04(gcc5.4)上编译时候发生的。这是C++ ABI一个错误,gcc4升gcc5时,std::string库接口做了迁移,而我工程中用了三方库tensorflow和protobuf,protobuf中用到了std::string。已经编好的tensorflow是用gcc4.8编的,gcc5上链接,接口对不上。两种解决方案:一种用gcc5重新编译三方库和工程,另一种就是在工程的编译选项(而不是链接选项)上加上-D_GLIBCXX_USE_CXX11_ABI=0参数,强制使用旧接口,问题解决。
包装迭代器
std::copy(tmp,tmp + 5,std::back_inserter(vec));
内存对齐
#pragma pack(4)
注意内存对齐指的是 起始地址 % min(4,类型长度) = 0
Magic Static
这种方法又叫做 Meyers' SingletonMeyer's的单例, 是著名的写出《Effective C++》系列书籍的作者 Meyers 提出的。所用到的特性是在C++11标准中的Magic Static特性:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.
如果当变量在初始化的时候,并发同时进入声明语句,并发线程将会阻塞等待初始化结束。
这样保证了并发线程在获取静态局部变量的时候一定是初始化过的,所以具有线程安全性。
如何在不修改代码的情况下访问,私有成员变量
或者取得私有成员变量的类型,利用模板神奇的特性做到
这里,有一个很大的问题就是,支持偷取多个private变量比较麻烦
另外decltype(Bank::money),这个只能在模板里生效,在模板外会被ban掉
#include <iostream>
class Bank {
	private:
		int money = 999;
	public:
		int getMoney() {
			return money;
		}
};
template <typename Bank,typename Money,Money Bank::*p>  
struct Thief {  
		friend Money& steal(Bank& bank) { 
			return bank.*p;  
		}  
}; 
int& steal(Bank&); 
template struct Thief<Bank,decltype(Bank::money),&Bank::money>;
int main(){
	using std::cout;
	using std::endl;  
  	Bank bank;
  	cout << bank.getMoney() << endl;
  	steal(bank) += 1;
  	cout << bank.getMoney() << endl;
	return 0;
}
整形的类型头文件
#include <cstdint>
std::uint64_t board;
SFINAE
所以问题的关键是sizeof,我们知道sizeof可以对类型也可以对表达式进行求编译期size,但有下面情况不会通过,函数类型,非全类型和void,以及位域。
void_t<A,B,C,D> 若所有类型通过sizeof的检查,返回void
enable_if_t = enable_if::type
enable_if<bool,type> 如果true就推导type,反之推断失败
如何判断一个类有没有某个成员函数 (模板元版)
/*
---- From XDU's mzb
*/
//#include <boost/any.hpp>
//using namespace boost;
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
template <typename Rng, typename T, typename enable = void>
struct has_find {
	using type = false_type;
};
template <typename Rng, typename T>
struct has_find<Rng, T, std::void_t<decltype(std::declval<Rng>().find(std::declval<T>()))>> {
	using type = true_type;
};
int main() {
	cout << has_find<vector<int>,int>::type() << "\n";
	cout << has_find<set<int>,int>::type() << "\n";
	return 0;
}
如何判断一个类有没有某个成员函数 (函数版)
c++11可以用
/*
---- From XDU's mzb
*/
//#include <boost/any.hpp>
//using namespace boost;
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
struct classa {
	string a()const{
		return "classa";
	}
};
struct classb {
	string b() const{
		return "classb";
	}
};
struct classc {
	string c() const{
		return "classc";
	}
};
template <typename Rng>
auto show_impl(const Rng& rng) -> decltype(rng.a()){
    return rng.a();
}
template <typename Rng>
auto show_impl(const Rng& rng) -> decltype(rng.b()){
    return rng.b();
}
template <typename Rng>
auto show_impl(const Rng& rng) -> decltype(rng.c()){
    return rng.c();
}
template <typename T>
void show(T const& rhs){
	cout << show_impl(rhs) << "\n";
}
int main() {
  	classa a;
  	classb b;
  	classc c;
  	show(a);
  	show(b);
  	show(c);
	return 0;
}
采用更丰富的std::string库
有时候,使用更好的库也表示使用额外的字符串函数。许多库都可以与 std::string 共同
工作,下面列举了其中一部分。
Boost 字符串库(http://www.boost.org/doc/libs/?view=category_String)
Boost 字符串库提供了按标记将字符串分段、格式化字符串和其他操作 std::string 的函数。这为那些喜爱标准库中的 
C++ 字符串工具包(http://www.partow.net/programming/strtk/index.html)
另一个选择是 C++ 字符串工具包(StrTk)。StrTk 在解析字符串和按标记将字符串分段
方面格外优秀,而且它兼容 std::string。
dlerror
直接返回上次so操作失败的原因
使用dlerror,dlopen,dlclose等函数需要加编译参数 -ldl
在保证lib的接口不变的情况下,调用者能在编译期检查出abi的不匹配
inline 命名空间,可以在保持调用端接口不变的情况下,使得链接段的符号变化
避免因为版本不匹配导致的诡异问题
Pimpl = Private Implementation | pointer to implementation
就是类内通过void*转发操作,这样接口类就不会改变,降低耦合
统计每一行代码的执行频率
相关的工具有gcov、gprof
g++  -fprofile-arcs -ftest-coverage -std=c++11 test.cpp -odemo
./demo
gcov test.cpp
rm test.gcda
rm test.gcno
获取时间
#include <ctime>
#include <string>
string GetTime(){
	time_t now = time(0);
	tm *ltm = localtime(&now);
	long long int tmp = 0;
	tmp += 1900 + ltm->tm_year;
	tmp *= 100;
	tmp += 1 + ltm->tm_mon;
	tmp *= 100;
	tmp += ltm->tm_mday;
	tmp *= 100;
	tmp += ltm->tm_hour;
	tmp *= 100;
	tmp += ltm->tm_min;
	tmp *= 100;
	tmp += ltm->tm_sec;
    string ret = to_string(tmp);
    ret.insert(12,":");
    ret.insert(10,":");
    ret.insert(8,"-");
    return ret;
}
raw string
需要打()的时候,在开头和末尾都添加一段一模一样的字符串即可
#include <iostream>
using namespace std;
int main()
  {
  	cout << R"(123\n\n\n\n)" << endl;
  	
  	cout << R"+*("(I'm raw string.)"
  
  
  
               new line)+*" << endl;
               
    cout << R"mzb123(()123)+++)mzb123";
    
  	return 0;
  }
User-defined literals 用户自定义后缀
限制非常多
#include <iostream>
using namespace std;
string operator "" _fz(unsigned long long int num) {
  return "["s + to_string(num) + "]";
}
int main()
  {
  	cout << 123_fz;
  	return 0;
  }
改变字体颜色
#pragma once
#ifdef _WIN32
#include <windows.h>
#include <iomanip>
struct ConsoleTextColor
  {
  	static constexpr WORD purple = FOREGROUND_BLUE  | FOREGROUND_RED        | FOREGROUND_INTENSITY;
  	static constexpr WORD blue   = FOREGROUND_BLUE  | FOREGROUND_GREEN      | FOREGROUND_INTENSITY;
  	static constexpr WORD red    = FOREGROUND_RED   | FOREGROUND_INTENSITY;
  	static constexpr WORD green  = FOREGROUND_GREEN | FOREGROUND_INTENSITY;
  	static constexpr WORD yellow = FOREGROUND_GREEN | FOREGROUND_RED        | FOREGROUND_INTENSITY;
  	static constexpr WORD white  = FOREGROUND_RED   | FOREGROUND_GREEN      | FOREGROUND_BLUE;  
  };
WORD GetConsoleTextColor()
  {
  	HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
  	CONSOLE_SCREEN_BUFFER_INFO info;
  	GetConsoleScreenBufferInfo(handle,&info);
  	return info.wAttributes;
  }
void SetConsoleTextColor(WORD color = ConsoleTextColor::white)
  {	
  	HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
	SetConsoleTextAttribute(hStdout,color);
  }
#define CreateConsoleTextColorFunction(color)       \
inline std::ostream& color(std::ostream &s) {      \
	SetConsoleTextColor(ConsoleTextColor::color);  \
  return s;                                         \
}
CreateConsoleTextColorFunction(purple);
CreateConsoleTextColorFunction(blue);
CreateConsoleTextColorFunction(red);
CreateConsoleTextColorFunction(green);
CreateConsoleTextColorFunction(yellow);
CreateConsoleTextColorFunction(white);
#elif defined(__linux__)
#include <string>
const std::string black = "\033[30m";
const std::string red = "\033[31m";
const std::string green = "\033[32m";
const std::string yellow = "\033[33m";
const std::string blue = "\033[34m";
const std::string purple = "\033[35m";
const std::string azure = "\033[36m";
const std::string white = "\033[37m";
#endif
#include <iostream>
int main()
  {
  	std::cout << yellow << "hello" << "\n";
  	std::cout << "test" << "\n";
  	std::cout << red << "hello" << "\n";
  	std::cout << white << "\n";
  	return 0;
  }
编译器参数

枚举
enum {
	a = 5,
	b = 3,
	c,
	d
}; 
a = 5,d = 5这就出问题了
struct Enum{
    enum{ One = 1, Two, Last };
};
struct EnumDeriv : public Enum{
    enum{Three = Enum::Last,Four,Five};
};
枚举继承
extern
修饰变量的链接特性
extern char g_str[] = "123456";   这 = 声明 + 定义
头文件只放声明
子类中调用父类模板函数
this -> template bark(0);
函数默认参数,只能写一次
在声明或者实现的时候写都行,同时允许c++ int g(ll = 20,double = 3.14);
/*
---- From XDU's mzb
*/
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
int g(ll = 20,double = 3.14);
int main()
  {
  	g();
	return 0;
}
int g(ll val,double pi)
  {
  	cout << val << " " << pi;
  	return val;
  }
override
强制继承虚函数
lambda
可以这么写
auto lambda = 
  [data = this -> data,&v = val]() mutable
  {
  	//  这里捕获了this 
  	data++;
  	v = 10;
  	// this -> data++;
	  };
functional-try块
主要用于可能抛出异常的构造函数
/*
---- From XDU's mzb
*/
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
int main()
try
  {
  	throw 10ll;
  	throw string("123");
  	return 0;
  }
catch(string const& rhs)
  {
  	cout << "ok" << "\n";
  }
catch(...)
  {
  	cout << "else" << "\n";
  }
命令规则
不要用汉语拼音(华为的招聘网站中有“paixubianliang”这样的绝绝子命名,绝绝子),全部用对应的英语
ll this_is_value = 10; 命名间隔采用下划线
void enum_sections()   函数名采取动词-名词的形式
define MAX(a,b) 宏全部大写,并且#define直接左对齐
成员变量前面加m_value
布尔表达式
以未定义形式来使用bool值可能会导致它处于既非true,又非false的状态
...挺鬼畜的
int main()
  {
  	bool b;
  	reinterpret_cast<char&>(b) = 128;
  	if (b != true)
  	  {
  	  	cout << "!= true\n";
		}
	if (b != false)
	  {
	  	cout << "!= false\n";
	  }
	if (b)
	  {
	  	cout << "b is true\n";
	  }
	if (!b)
	  {
	  	cout << "b is false\n";
	  }
	return 0;
}
在if,for,等中避开隐式的类型转换,比如
HANDLE handle = Open(...);
if (handle)
这里有一个HANDLE到bool的隐式转换,问题在于文件句柄的无效值是0xFFFFFFFF
为了避开可能的错误,在这里避开隐式的向bool转换
经典assert(i = 5),这种玩意全部写成assert(5 == i)
有时候很容易手抖,写一个这种玩意,很难搞
还有
c++的异常处理体系
析构函数不能也不应该抛出异常
如果你的构造不得不抛出异常,把可能抛出异常的部分弄成一个函数,提前处理掉可能抛异常的部分
或者在析构函数里面捕获所有异常
如何处理构造函数抛出异常
c++11中noexcept取代了throw()
还可以通过throw(int,out_of_range)等限定抛出异常的可能性如果抛出预期之外的一场直接终止进程,感觉比较鸡肋
并且限定符可以修饰函数类型,函数指针
位于#include<exception>
virtual ~exception(){};
virtual const char* what() const;

一个基本的异常体系扩展
struct ex_out_of_range : public out_of_range
  {
  	char str[100];
  	ex_out_of_range(ll n)
  	  :out_of_range("base::out_of_range")
		{
			memset(str,0,sizeof(str));
			ll i = 0;
			while (n)
			  {
			  	str[i++] = '0' + n % 10;
			  	n /= 10;
			  }
		}
	virtual const char* what() const noexcept
	  {
	  	return str;
	  }
  };
一个相对完善的异常处理类(以及附加的一些东西)应该能够处理下面的一些功能:
1) 能够方便的定义异常类的继承树
2) 能够方便的throw、catch,也就是在代码中捕获、处理代码的部分应该更短
3) 能够获取异常出现的源文件的名字、方法的名字、行号
4) 能够获取异常出现的调用栈并且打印出来
需要自己设计一套这个玩意,先咕咕咕
new
除了palacement new还有palacement delate
如何让new不抛出异常,c++11
auto p = new(std::nothrow) string[10000000000ll];
不能用c++11的话,目前没什么好办法
不过可以利用set_new_handler来设置出错的时候,首先调用自定义函数
extern c
还有extern c++
断点技巧
windows的断点指令是int 3
windows下有一个包装断点的函数,这个函数有一个问题是进入该函数才触发断点,不方便直接定位到真实的触发位置
更好的办法是inline断点函数,这样就可以定位真实的触发位置
void *p;
assert(p);
会触发一个截断警告
断点最好带上简单的信息
方法1
assert("wrong answer" and 10 == 20); 
方法2
ll this_value_is_check_answer = 10;
assert(20 == this_value_is_check_answer);
显然方法一好很多
如何展开可变参数宏
base代码
/*
---- From XDU's mzb
*/
#include <bits/stdc++.h>
using namespace std;
using ll = long long int;
template<typename T>
void print(T const& rhs)
  {
  	cout << rhs << "\n";
  }
template<typename T,typename ...Args>
void print(T const& rhs,Args ... args)
  {
  	print(rhs);
  	print(args...);
  }
int main()
  {
  	print("123",123,112.12);
	return 0;
  }
这个比较秀
template<typename ...Args>
void print(Args ... args)
  {
  	std::initializer_list<int>{(print(args),0)...};
  }
还可以用std::initializer_list直接做包扩展,这里也可以直接lamdba捕获args
template<typename... Args>
void expand(Args... args)
{
   std::initializer_list<int>{(cout << args << endl, 0)...};
}
还可以模板元编程 + tuple + enable_if输出
本文来自博客园,作者:XDU18清欢,转载请注明原文链接:https://www.cnblogs.com/XDU-mzb/p/15525697.html
 
                    
                 
                
            
         浙公网安备 33010602011771号
浙公网安备 33010602011771号