11 KiB
使用c++11消除重复,提高代码质量
3.1 type_traits———类型萃取
type_traits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类,在很多场合都会使用到。
3.1.1 基本的type_traits
1.基本的type_traits
无需自己定义static const int或者enum类型,只需要从std::integral_constant派生:
template<typename Type>
struct GetLeftSize : std::integral_constant<int,1>
之后用GetLeftSize::value来取值。
2.类型判断的type_traits
这些从std::integral_constant派生的type_traits,可以通过std::is_xxxx来判断T的类型。也可以通过std::is_xxxx::value是否为true来判断模板类型是否为目标类型。
#include <iostream>
#include <type_traits>
int main(){
std::cout<<"is_const:"<<std::endl;
std::cout<<"int:"<<std::is_const<int>::value<<std::endl;
std::cout<<"const int:"<<std::is_const<const int>::value<<std::endl;
std::cout<<"const int&:"<<std::is_const<const int&>::value<<std::endl;
std::cout<<"const int*:"<<std::is_const<const int*>::value<<std::endl;
std::cout<<"int* const:"<<std::is_const<int* const>::value<<std::endl;
}
3.判断两类型之间的关系traits
struct is_same;//判断两类型是否相同
struct is_base_of;//判断一个是否为另一个的基类
struct is_convertible;//判断模板参数类型能否转化为后面的模板参数类型
4.类型转换traits
常用的类型的转换traits包括对const的修改————const的移除与添加,引用的修改————引用的移除与添加,数组的修改和指针的修改。
创建对象时需要去除引用:
template<typename T>
typnename std::remove_reference<t>::type* Create()
{
typedef typename std::remove_reference<t>::type U;
return new U();
}
模板参数T可能是引用类型,而创建对象时,需要原始的类型,不能用引用类型,所以需要将可能的引用引用。同样const也需要考虑进去。
为了简洁可以使用std::decay,移除const与引用。
另一个用处就是用来保存函数指针。
{
using FnType=typename std::decay<F>::type;
SimpFunction(F& f):m_fn(f){
}
void run()
{
m_fn();
}
FnType m_fn;
}
3.1.2根据条件选择traits
template<bool B,class T,class F>
struct conditional;
typedef std::conditional<true,int,float>::type A;//int
3.1.3获取可调用对象返回类型的traits
一些模板函数的返回值不能直接确定,虽然可以通过decltype来进行推断,但是可读性较差。
可以通过auto -> decltype进行和简化。
template<typename F,template Arg>
auto Func(F f,Arg arg)->decltype(f(arg))
{
return f(arg);
}
不过在类没有构造函数的情况下就不行了。此时如果希望编译器进行推导,就需要使用std::declval。
decltype(std::declval<int>(std::declval<int>())) i=4;
declval获得的临时值引用不能用于求值,因此需要使用decaltype来推断出最终值。
当然可以用更加简洁的方法:
std::result_of<A(int)>::type i=4;
3.1.4 根据条件禁用或者启用某种或者某些类型traits
template<class T>
typnename std::enable_if<std::is_arithmetic<T>::value,T>::type foo(T t)
{
return t;
}
auto r=foo(1);
auto r1=foo(1.2);
auto r2=foo("test");//error
不但可以作用于返回值,也可以作用于模板定义、类模板特化和入参类型的限定。
3.2 可变参数模板
在c++11中允许模板定义中包含0到任意个数模板参数。声明可变参数模板需要在typename或class后面带上省略号 省略号作用有两个:
- 声明一个参数包,这个参数包中可以包含0到任意个模板参数。
- 在模板定义的右边,可以将参数包展开成一个一个独立参数。
3.2.1 可变参数模板函数
template<class... T>
void f(T... args)
{
std::cout<<sizeof...(args)<<std::endl;
}
f(); //0
f(1,2); //2
f(1,2.5,""); //3
- 递归函数方式展开参数包
void print()
{
std::cout << "empty" << std::endl;
}
template<class T, class... Args>
void print(T head, Args... rest)
{
std::cout << "parameter" << head << std::endl;
print(rest...);
}
int main()
{
print(1,2,3,4,5);
return 0;
}
当然递归终止函数也可以写成,或者使用更多参数
template<typename T,typename T1>
void print(T t,T1, t1)
{
std::cout<<t<<""<<t1<<std::endl;
}
- 逗号表达式和初始化列表方式展开参数包
逗号运算符:
运算符有左右两个表达式,先计算左侧的,之后丢弃结果再计算右侧。
d=(a=b,c);
b会先赋值给a,接着括号中的逗号表达式将会返回c的值。
template <class T>
void printarg(T t)
{
std::cout<<t<<std::endl;
}
template <class ...Args>
void expand(Args... args)
{
int arr[]={(printarg(args),0)...};
}
expand(1,2,3,4);
这里使用了c++11的初始化列表特性,{(printarg(args),0)...}将会展开成{(printarg(args1),0),(printarg(args2),0),(printarg(args3),0),(printarg(args4),0),}
之后也可以使用std::initializer_list来优化,这样就无需定义一个数组了,使用lambda的话printarg函数就无需定义了。
3.2.2可变参数模板
std::tuple是一个可变参数模型
template<class... Type>
class tuple;
- 模板递归和特化方式展开参数包
//前向声明
template<typename... Args>
struct Sum;
//基本定义
template<typename First,typne... Rest>
struct Sum<First.Rest...>
{
enum{value=Sum<First>::value+Sum::<Rest...>::value};
};
//递归终止
template<typename Last>
struct Sum<Last>
{
enum{value=sizeof(Last)};
};
- 继承方式展开参数包
有点复杂不做笔记了
除了用继承还可以使用using来实现。
template<int N,int... Indexes>
struct MakeIndexes{
using type=MakeIndexes<N-1,N-1,Indexes...>::type
};
template<int... Indexes>
struct MakeIndexes<0,Indexes...>
{
using type=IndexSeq<Indexes...>;
};
MakeIndexes<3>=>IndexSeq<0,1,2>
3.2.3可变参数模板
免去手写大量的模板类代码,而让编译器来自动实现。
template<typename... Args>
T* Instance(Args... args)
{
return new T(args);
}
A* pa=Instance<A>(1);
B* pb=Instance<B>(1,2);
上面代码中的Args是拷贝值,使用右值引用与完美转发来消除损耗。
template<typename... Args>
T* Instance(Args&&... args)
{
return new T(std::forward<Args>(args)...);
}
3.3 可变参数模板和type_taits的综合应有
3.3.1 optional的实现
使用placement new的时候容易出现内存对齐的问题,可以使用:
#include <iostream>
#include <type_traits>
struct A
{
int avg;
A(int a,int b):avg((a+b)/2){}
};
typedef std::aligned_storage<sizeof(A),alignof(A)>::type Aligned_A;
int main()
{
Aligned_A a,b;
new (&a) A(10,20);
b=a;
std::cout<<reinterpret_cast<A&>(b).avg<<std::endl;
return0;
}
3.3.2 惰性求值类Lazy的实现
#ifndef _LAZY_HPP_
#define _LAZY_HPP_
#include <boost/optional.hpp>
template<typename T>
struct Lazy
{
Lazy(){}
template <typename Func, typename... Args>
Lazy(Func& f, Args && ... args)
{
m_func = [&f, &args...]{return f(args...); };
}
T& Value()
{
if (!m_value.is_initialized())
{
m_value = m_func();
}
return *m_value;
}
bool IsValueCreated() const
{
return m_value.is_initialized();
}
private:
std::function<T()> m_func;
boost::optional<T> m_value;
};
template<class Func, typename... Args>
Lazy<typename std::result_of<Func(Args...)>::type>
lazy(Func && fun, Args && ... args)
{
return Lazy<typename std::result_of<Func(Args...)>::type>(
std::forward<Func>(fun), std::forward<Args>(args)...);
}
#endif
#include "Lazy.hpp"
#include <iostream>
#include <memory>
struct BigObject
{
BigObject()
{
std::cout << "lazy load big object" << std::endl;
}
};
struct MyStruct
{
MyStruct()
{
m_obj = lazy([]{return std::make_shared<BigObject>(); });
}
void Load()
{
m_obj.Value();
}
Lazy<std::shared_ptr<BigObject>> m_obj;
};
int Foo(int x)
{
return x * 2;
}
void TestLazy()
{
//带参数的普通函数
int y = 4;
auto lazyer1 = lazy(Foo, y);
std::cout << lazyer1.Value() << std::endl;
//不带参数的lamda
Lazy<int> lazyer2 = lazy([]{return 12; });
std::cout << lazyer2.Value() << std::endl;
//带参数的fucntion
std::function < int(int) > f = [](int x){return x + 3; };
auto lazyer3 = lazy(f, 3);
std::cout << lazyer3.Value() << std::endl;
//延迟加载大对象
MyStruct t;
t.Load();
}
int main(void)
{
TestLazy();
system("pause");
return 0;
}
3.3.3 dll帮助类实现
首先需要封装GetProcAddress函数,需要解决:
- 函数的返回值可能是不同类型,如果以一种通用的返回值来消除这种不同返回值导致的差异
- 函数的入参数目可能任意个数,且类型也不尽相同,如何来消除入参个数和类型的差异
#ifndef _DLLPARSER_HPP_
#define _DLLPARSER_HPP_
#include <Windows.h>
#include <string>
#include <map>
#include <functional>
#include <iostream>
class DllParser
{
public:
DllParser()
{
}
~DllParser()
{
UnLoad();
}
bool Load(const std::string& dllPath)
{
m_hMod = LoadLibrary(dllPath.data());
if (nullptr == m_hMod)
{
std::cout << "LoadLibrary failed\n";
return false;
}
return true;
}
template <typename T, typename... Args>
typename std::result_of<std::function<T>(Args...)>::type
ExcecuteFunc(const std::string& funcName, Args&&... args)
{
auto f = GetFunction<T>(funcName);
if (f == nullptr)
{
std::string s = "can not find this function " + funcName;
throw std::exception(s.c_str());
}
return f(std::forward<Args>(args)...);
}
private:
bool UnLoad()
{
if (m_hMod == nullptr)
return true;
auto b = FreeLibrary(m_hMod);
if (!b)
return false;
m_hMod = nullptr;
return true;
}
template <typename T>
T* GetFunction(const std::string& funcName)
{
auto addr = GetProcAddress(m_hMod, funcName.c_str());
return (T*) (addr);
}
private:
HMODULE m_hMod;
std::map<std::string, FARPROC> m_map;
};
#endif //_DLLPARSER_HPP_
3.3.4 lambda链式调用
#include <iostream>
#include <functional>
#include <type_traits>
template<typename T>
class Task;
template<typename R, typename...Args>
class Task<R(Args...)>
{
public:
Task(std::function<R(Args...)>&& f) : m_fn(std::move(f)){}
Task(std::function<R(Args...)>& f) : m_fn(f){}
template<typename... Args>
R Run(Args&&... args)
{
return m_fn(std::forward<Args>(args)...);
}
template<typename F>
auto Then(F& f)->Task<typename std::result_of<F(R)>::type(Args...)>
{
using return_type = typename std::result_of<F(R)>::type;
auto func = std::move(m_fn);
return Task<return_type(Args...)>([func, f](Args&&... args)
{return f(func(std::forward<Args>(args)...)); });
}
private:
std::function<R(Args...)> m_fn;
};
void TestTask()
{
Task<int(int)> task = [](int i){ return i; };
auto result = task.Then([](int i){return i + 1; }).Then([](int i){
return i + 2; }).Then([](int i){return i + 3; }).Run(1);
std::cout << result << std::endl; // 7
}
int main(void)
{
TestTask();
system("pause");
return 0;
}
后面若干章节较难,为了节约时间所以跳过了