各种c++技巧

说明

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输出


posted @ 2021-11-08 19:13  XDU18清欢  阅读(113)  评论(0)    收藏  举报