This commit is contained in:
2023-06-29 11:55:02 +08:00
commit 36e95249b1
1236 changed files with 464197 additions and 0 deletions

View File

@@ -0,0 +1,351 @@
地平线的世界设计
1、开方世界——玩家有很大的探索区域
2、昼夜循环
3、动态天气
4、史诗般的场景
5、天空是地形的一部分
•地平线是一个巨大的开放世界,你可以去任何你看到的地方,包括山顶。(我表示呵呵,这个已经被吐槽,真正做到的只有荒野之息了)
•由于这是一个活生生的现实世界,我们模拟地球的自转周期。
•天气是环境的一部分,因此它也会发生变化和演变。
•有很多史诗般的风景:高山,森林,平原和湖泊。
•天空是地平线的一个重要组成部分。他们占了屏幕的一半。天空也是故事和世界建筑的重要组成部分。
云的实现目标
1、美术方面的可操控性
2、真实性需要实现多种云的类型
3、与天气继承
4、可移动的
5、EPIC叫我干什么
早期的模拟方案:
1、使用流体解算器MAYA、houdini效果很好。但是工作室模拟云彩的经验比较少……说到底就是没钱请不来影视特效大佬
2、使用简单的几何体配合体素然后运行流体解算器直到他们像云。
3、然后我们开发了一种照明模型用于预先计算主和次散射。在Houdini完成解算需要10S时间
4、把云转换层多边形烘培了光照等数据。再使用Billboards————缺点无法轻易生成云阴影本人表示严重质疑
5、我们试着把我们所有的voxel云作为一个云来生产它也可以混入大气层。类型的工作。在这一点上我们后退一步来评估什么行不通。没有一个解决方案使云随着时间的推移而进化。没有一种好的方法可以使云层通过头顶。并且对所有方法都有很高的内存使用和透支。所以也许传统的基于资产的方法是不可取的。
6、于是开始考虑体素云了
7、所以我们进入了Houdini并从模拟的云形状中生成了一些3d纹理。使用Houdini的GL扩展我们构建了一个原型GL着色来开发云系统和照明模型。
8、这让我们看到了地平线的云系统。为了更好地解释我把它分成了四个部分:建模、照明、渲染和优化。在了解我们对云的建模之前,我们应该对云是什么以及它们如何演变成不同的形状有一个基本的了解。
9、There are many examples of real-time volume clouds on the internet. The usual approach involves drawing them in a height zone above the camera using something called fBm, Fractal Brownian Motion. This is done by layering Perlinnoises of different frequencies until you get something detailed.
•This noise is then usually combined somehow with a gradient to define a change in cloud density over height.
10、由于光噪声本身不能切割我们开发了自己的分层噪音。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/32C9E5FA800B471DAA04DDFC66AE5542.png)
Worley噪音是由Steven Worley在1996年引入的经常用于因果关系和水的影响。如果它是倒立的就像你在这里看到的:
它的形状非常紧密。
我们把它像标准的perlinfbm方法一样分层
然后我们用它来作为对dilate perlin噪声的偏移量。这使我们能够保持perlin噪声的连接但是增加了一些billowy的形状。
我们把这叫做“佩林-沃利”的噪音
11、在游戏中把声音存储为tiling 3d纹理通常是最好的。
•你想保持纹理读取到最低…
•尽可能小的保持决心。
•在我们的例子中我们已经压缩的声音…
•两个3 d纹理…
•和1 2 d纹理。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C055723666034AE096C4B945D18D726B.png)
第一个3 d纹理…
•4个通道
•这是128 ^ 3决议…
第一个频道是我刚才描述的perlin- worley噪声。
另外3个是在频率增加的Worley噪声。和标准方法一样这个3d纹理用来定义云的基本形状。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B89809EC8BAA4624B0F4ADDEDD88DB1D.png)
我们的第二个3 d纹理…
•有3个频道…
•它是32 ^ 3决议…
•使用Worley噪音来增加频率。这个纹理被用来向第一个3d噪音定义的基本云形状添加细节。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/7D9FC6AB9E1B4C679B6C79627DCAFE64.png)
•我们的2 d纹理…
•有3个频道…
•这是128 ^ 2分辨率…
•并使用curl噪音。它不是发散的是用来假装流体运动的。我们使用这种噪音来扭曲我们的云形状并增加气流的感觉。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C6719605F51941139F16BAD78E97E0C4.png)
回想一下,标准解决方案要求高度梯度来改变高度的噪音信号。相反,我们使用…
•3数学预设,代表了主要的低空…
•当我们在样本位置混合时,云的类型。
•我们也有一个值告诉我们希望在样本位置上有多少云覆盖。这是0和1之间的值。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/1AD13B896C2544DBB2DAC66417C158A8.png)
我们在屏幕右边看到的是一个大约30度的视图。我们将会在相机上方的一个区域中按标准方法绘制云。
首先我们建立一个基本的云形状通过采样我们的第一个3d纹理并将它乘以我们的高度信号。
•下一步是将结果乘上覆盖范围,并减少云层底部的密度。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/229424181EA1428C901DB709DF5DBFE6.png)
使用2D燥波增加基础云的细节
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B5B4D5E3DD0B4C5496A51E591CABDDBD.png)
•通过在云边缘减去第二个3d纹理来侵蚀基本的云形状。小提示如果你在云的基础上颠倒了Worley的噪音你会得到一些很好的声音形状。
•我们也扭曲第二旋度由2 d纹理噪声假纠结的扭曲的大气湍流可以看到…
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/41DD539373F943D28BBDC56D69B5F70F.png)
在这个图像中你可以看到一个小地图在左下角。这代表了在我们的世界地图上驱动云层的天气设置。您所看到的粉红色白色图案是来自我们的天气系统的输出。红色是覆盖层,绿色是降水,蓝色是云类型。天气系统通过模拟在游戏中进行的模拟来调节这些通道。这里的图像有积雨云直接在头顶(白色)和普通积云在远处。我们有控制偏见的方法来保持事物在一般意义上是直接的。
其实就是通过贴图来控制雨云的分布
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/57857EB5097C4544A78B8A4384CE4E4A.png)
我们还利用我们的天气系统来确保云是地平线,这是很有趣的。
•我们画的cloudscape withina球员…周围半径35000米。
•和从15000米的距离…
•我们开始向积云过渡到50%的覆盖率。
总结
所以总结我们的建模方法…
我们遵循标准的ray - march / sampler框架
但是我们用两个层次的细节来构建云
低频率的云基形状
高频细节和失真
我们的噪音是由Perlin、Worley和Curl杂音制成的
我们为每个云类型使用一组预先设置来控制在高度和云覆盖上的密度
这些都是由我们的天气模拟或定制纹理来驱动的,用于使用剪切场景
它在给定的风向中都是动态的。
云的定向散射或发光质量…
•银衬里当你看向太阳通过云…
当你从太阳中移开时,云上的暗边可以看到。
前两个有标准的解决方案,但第三个是我们必须解决的问题。
在光线进入云再到结束,要么被吸收、要么色散
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/45AD92FC56E94A48A02E4CFF452C11DE.png)
Beer's law说我们可以根据所经过的介质的光学厚度来确定光的到达点。根据比尔斯定律我们有一种基本的方法来描述云中的特定点的光量。
•如果我们用能量来替代云中的透光率,并把它画出来,你就可以看到能量指数的下降。这是我们照明模型的基础。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/CECBE3176ED0405185377E0DA67CB4C0.png)
在云层中,有更高的光散射的可能性。这被称为各向异性散射。
在1941年henyey - greenstein模型被开发用来帮助天文学家在星系尺度上进行光计算但今天它被用于在云照明中可靠地复制各向异性。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/DCCE3D5B67DB4F45AF5098B742371AD1.png)
每次我们采样光能量时我们用henyey - greenstein相函数乘以它。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/CDF9EA6FEA9347CE9D8AD1ADD7ACBE88.png)
在这里你可以看到结果。左边的只是我们照明模型的Beers law部分。在右边我们应用了henyey - greenstein相函数。请注意在右边的太阳周围的云层更明亮。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/671F3D41178E45C5BE7ED6753DA19EE6.png)
当我们在云上越走越深时,我们在散射上的潜力就会增加,更多的信息将会到达我们的眼睛。
•如果你把这两个函数得到描述这样的东西…
•效应以及传统方法。
我还在ACM数字图书馆寻找啤酒粉的近似方法但我还没有发现任何与这个名字有关的东西。
将Power公式与Beer's law相结合得到了Beer's power
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/996CDD462EAE44A0B81AF08E2F1E61BB.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/3C34E5D14A164FB1A2A5EC0EAC226033.png)
但我们必须记住这是一个依赖于视图的效果。我们只看到我们的视图矢量接近光矢量所以Power函数也应该解释这个梯度。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/4BA5F7C597C3427D9B88C1997A362875.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/0F8D74F9099449409DA027F8E8433F64.png)
So, in review our model has 3 components:
•Beers Law
•Henyen-Greenstein
•our powder sugar effect
•And Absorption increasing for rain clouds
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/0D33BE22800A49659E2B3F1846C0CCC3.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/693A2257D0094575A428827A016248A2.png)
低海拔1500~4000用体素云。超过4000就用2D ray march云
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/C33B04DE3E5D4F42ABE11A792C015432.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/EE882FE8787B4926B7FC23B50E3E843E.png)
回想一下,采样器有一种低细节的噪声,它能形成基本的云形状
•高细节的噪音,增加我们所需要的现实细节。
高细节的噪声总是应用于从基云形状边缘的侵蚀。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/5682BFB95ED249878E6C9AD2825B4A67.png)
这意味着我们只需要做高细节的噪声和所有相关的指令,其中低细节的样本返回一个非零的结果。
•这就产生了在我们的云所处的区域周围产生一个等表面的效应。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/E1A53D8D6463443A88F411BB0EBA42F1.png)
因为射线长度增加我们看向地平线,我们开始…
•一个初始potential64样本和结束…
•潜在的128位。我之所以说潜力是因为这些优化可能导致3月份提前退出。我们真的希望他们能做到。
这就是我们如何利用样本来建立我们图像的alpha通道。为了计算光强度我们需要取更多的样品。
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/B558F80C8F9A466995AB61AF820A9363.png)
![](D:/youdaonote-pull-master/youdaonote/youdaonote-images/4D891FCBA47D4146BE72624E3B2BC16D.png)
In our approach, we sample 6 times in a cone toward the sun. This smooth's the banding we would normally get with 6 simples and weights our lighting function with neighboring density values, which creates a nice ambient effect. The last sample is placed far away from the rest …
•in order to capture shadows cast by distant clouds.
自阴影算法…………

View File

@@ -0,0 +1,494 @@
## 1.1.1 auto笔记
### auto与引用作用则不会抛弃const
```
int x=0;
const auto& g=x; //g->const int&
auto& h =g; //h->const int&
```
### 什么时候用auto
比如在使用迭代器遍历的时候,对类型及获取并不关心的时候。
### auto推断模板函数对应函数返回值
```
struct Foo {
static int get(void) { return 0; }
};
struct Bar {
static const char* get(void) { return "0"; };
};
struct Bar2 {
};
template <class A>
void func(void)
{
auto val = A::get();
}
int main()
{
func<Foo>();
func<Bar>();
func<Bar2>();//Bar2类中不包含get方法此时编译器会报错
system("pause");
return 0;
}
```
### c++14 增加了函数返回值推导
在C++11下如果你想要打印出一个数的平方可能需要这样
```
auto square_int = [](int x) { return x * x; };
auto square_double = [](double x) { return x * x; };
std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;
```
为了保持函数的局部性我们才选择的lambda但C++11的lambda却导致多个类型时代码膨胀且重复此时我们需要回过头来借助全局的模板了。
但C++ 14可以完美的解决上面的问题因为C++14中lambda的参数可以用auto代替具体的类型
```
auto square = [](auto x) { return x * x; };
std::cout<<square_int(10)<<std::endl;
std::cout<<square_int(10.1)<<std::endl;
```
## 1.1.2 decltype
### decltype推导规则
decltype(exp)
1. exp是标识符、类访问表达式decltype(type)和exp类型一样。
2. exp是函数调用decltype(exp)和返回值的类型一致。
3. 其他情况若exp是一个左值则decltype(exp)是exp类型的左值引用否则和exp类型一致。
```
class Foo{
static const int Number=0;
int x;
};
int n=0;
volatile const int &x=n;
decltype(n) a=n; //a->int
decltype(x) b=n; //b->const volatile int &
decltype(Foo::Number) c=0; //c->const int
Foo foo;
decltype(foo.x) d=0; //d->int
```
```
int& func_int_r(void);
int&& func_int_rr(void);
int func_int(void);
const int& func_cint_r(void);
const int&& func_cint_rr(void);
const int func_cint(void);
const Foo func_cfoo(void);
int x=0;
decltype(func_int_r()) a1=x; //a1->int &
decltype(func_int_rr()) b1=x; //b1->int &&
decltype(func_int()) c1=x; //c1->int
decltype(func_cint_r()) a2=x; //a2->const int &
decltype(func_cint_r()) b2=x; //b2->const int &&
decltype(func_cint()) c2=x; //c2->int & 因为是纯右值所以舍弃掉cv符只有类类型可以携带
decltype(func_cfoo()) ff=foo(); //ff->const foo
```
```
struct Foo{int x;};
const Foo foo=Foo();
decltype(foo.x) a=0; //a->int
decltype((foo.x)) b=a; //b->const int &
int n=0,m=0;
decltype(n+m) c=; c=0; //c->int
decltype(n+=m) d=c; d=c; //d->int &
```
a和b的结果仅仅多加了一对括号他们得到的类型却是不同的。具体解释请见P12
++++
### decltype实际应用
具体解释请见P12
## 1.1.3 返回类型后置语法——auto和decltype结合使用
如何实现类似:
```
template <typename R,typename T,typename U>
R add(T t,U u)
{
return t+u;
}
int a=1;floatb=2.0;
auto c=add<decltype(a+b)>(a+b);
```
错误,定义返回值时参数变量还不存在
```
template <typename T,typename U>
decltype(t+u) add(T t,U u)
{
return t+u;
}
```
正确,但是如果T和U是无参构造函数的类就不行了
```
template <typename T,typename U>
decltype(T()+U()) add(T t,U u)
{
return t+u;
}
```
使用c++返回类型后置语法
```
template <typename T,typename U>
auto add(T t,U u)->decltype(t+u)
{
return t+u;
}
```
另一个例子,根据使用的重载函数推导返回类型
```
int& foo(int& i);
float foo(float& f);
template<typename T>
auto func(T& val)-> decltype(foo(val)
{
return foo(val);
}
```
### 1.2.2 模板的别名
using和typedef都可以定义一个新的类型别名但是using可以直接定义一个模板别名而不像typedef需要一个struct 外敷类来实现。而且using的可读性更高。
```
template <typename T>
struct func_t{
typedef void (*type)(t,t);
};
func_t<int>::type xx_1;
template<typename T>
using func_t=void(*)(T,T);
func_t<int> xx_2;
```
另外using可以定义任意类型的模板表达方式
```
template<typename T>
using type_tt = T;
type_tt<int> a;
```
### 1.3.2初始化列表的使用细节
初始化列表可以用于对应类型函数的返回值中。
例如就等于Foo123,321.0假设有Foointfloat这个构造函数
```
Foo function(void)
{
return {123,321.0};
}
```
### 1.3.3初始化列表
自己定义的类并没有类似Vector、Map容器的任意长度的初始化功能如果想要实现可以添加一下构造函数来实现。
```
class Foo
{
public
Foo(std::initializer_list<int> list){
}
};
```
之后就可以执行Foo foo={1,2,3,4,5,6,7}(需要自己实现list对类内部变量的赋值)
同时std::initializer_list可以用于传值
```
void func(std::initializer_list<int> l){
for(auto it=l.begin();it!=l.end();++it)
{std::cout<<*it<<std::endl;}
}
int main()
{
func({});
func({1,2,3})
}
```
PS.std::initializer_list为了提高初始化效率使用的是引用传值所以当传值结束时这些里面的变量也都被销毁了。所以如果需要传值这需要传递它初始化容器的值。
初始化列表同时可以用于检查类型收窄,即因为类型转换而导致的数据丢失。
### 1.4.1基于范围的for循环
当遍历需要修改容器中的值时,则需要使用引用:
```
for(auto& n: arr)
{
std::cout<<n++<<std::endl;
}
```
如果只是希望遍历而不希望修改可以使用const auto& n
### 1.4.2基于范围的for循环使用细节
在:后面可以写带返回遍历对象的函数,而且这个函数只执行一次。
```
#include <iostream>
#include <vector>
std::vector<int> arr={1,2,3,4,5,6};
std::vector<int>& get_range()
{
std::cout<<"get_range->"<<std::endl;
return arr;
}
int main()
{
for(auto val: get_range())
{
std::cout<<val<<std::endl;
}
return 0;
}
```
在使用基于范围的for循环时如果遍历时修改容器大小很可能出现因为迭代器失效而导致的异常情况。
### 1.4.3 让基于范围的for循环支持自定义类型
```
#include <iostream>
namespace detail_range {
template <typename T>
class iterator {
public:
using value_type = T;
using size_type = size_t;
private:
size_type cursor_;
const value_type step_;
value_type value_;
public:
iterator(size_type cur_start, value_type begin_val, value_type step_val)
:cursor_(cur_start), step_(step_val), value_(begin_val)
{
value_ += (step_*cursor_);
}
value_type operator*() const { return value_; }
bool operator!=(const iterator& rhs) const
{
return (cursor_!=rhs.cursor_);
}
iterator& operator++(void)
{
value_ += step_;
++cursor_;
return (*this);
}
};
template <typename T>
class impl {
public:
using value_type = T;
using reference = const value_type&;
using const_reference = const value_type&;
using iterator = const detail_range::iterator<value_type>;
using const_iterator= const detail_range::iterator<value_type>;
using size_type = typename iterator::size_type;//这里的typename是告诉编译器后面跟着的是类型
private:
const value_type begin_;
const value_type end_;
const value_type step_;
const size_type max_count_;
size_type get_adjusted_count(void) const
{
if (step_ > 0 && begin_ >= end_)
throw std::logic_error("End value must be greater than begin value.");
else if (step_ < 0 && begin_ <= end_)
throw std:: logic_error("End value must be less than begin value.");
size_type x = static_cast<size_type>((end_ - begin_) / step_);
if (begin_ + (step_*x) != end_)
++x;//为了防止出现类似{0,12,0.5}之类的迭代要求,而做的升位处理
return x;
}
public:
impl(value_type begin_val, value_type end_val, value_type step_val)
:begin_(begin_val),
end_(end_val),
step_(step_val),
max_count_(get_adjusted_count())
{}
size_type size(void) const
{
return max_count_;
}
const_iterator begin(void) const
{
return{ 0,begin_,step_ };
}
const_iterator end(void) const
{
return{ max_count_,begin_,step_ };
}
};
template <typename T>
detail_range::impl<T> range(T end)
{
return{ {},end,1 };
}
template <typename T>
detail_range::impl<T> range(T begin, T end)
{
return{ begin,end,1 };
}
template <typename T,typename U>
auto range(T begin, T end, U step)->detail_range::impl<decltype(begin + step)>
{
using r_t = detail_range::impl<decltype(begin + step)>;
return r_t(begin, end, step);
}
}
int main()
{
for (int i : detail_range::range(15))
{
std::cout << " " << i;
}
std::cout << std::endl;
for (auto i : detail_range::range(2,8,0.5))
{
std::cout << " " << i;
}
std::cout << std::endl;
system("pause");
return 0;
}
```
### 1.5.2 可调用对象包装器———std::function
用于解决以下可调用对象的统一操作的问题
1. 函数指针
2. 具有operator() 成员函数的类对象(仿函数)
3. 可被转化为函数指针的类对象
4. 类成员(函数)指针
std::function也可以作为函数参数来使用也就是相当于回调函数的作用
### 1.5.3 std::bind绑定器
std::bind用来将可调用对象与其参数一起进行绑定。绑定后的结果可以用std::function进行保存并延迟调用到任何我们需要调用的时候。
作用:
1. 将可调用对象与其他参数一起绑定成一个仿函数
2. 将多元可调用对象转成一元或者(n-1)元可调用对象,即只绑定部分参数。
关于bind的使用
bind&要调用的函数,&对象, 要调用函数的参数1要调用函数的参数2...,_1(bind函数的参数1),_2(bind函数的参数2)...)
如果bind的是一个非静态成员函数第二个参数一定是一个该成员的一个指针后面才是正常的参数。
数this指针作为一个隐含参数传递给非静态成员函数用以指向该成员函数所属类所定义的对象。当不同对象调用同一个类的成员函数代码时编译器会依据该成员函数的this指针所指向的不同对象来确定应该一用哪个对象的数据成员。下面举一个简单例子。
1bind&f)() 假设f是一个全局函数绑定全局函数并调用
2bind (&A::f, A())() 假设A是一个构造函数为空的类这个形式绑定了类的成员函数故第二个参数需要传入一个成员成员静态函数除外
### 1.6 lambda表达式
```
[capture](params)opt->ret{body;};
auto f=[](int a)->int {return a+1;};
```
c++11允许省略lambada表达式的返回值定义
```
auto x1=[](int i) {return i;};
auto x2=[](){return {1,2};); //error:初始化列表无法推断
```
在没有参数列表时,()是可以省略的
```
auto f1=[]{return 1;};
```
```
class A{
public:
int i_=0;
void func(int x,int y)
{
auto x1=[]{return i_;}; //error,没有捕获外部变量
auto x2=[=]{return i_+x+y;}; //ok
auto x3=[&]{return i_+x+y;}; //ok
auto x4=[this]{return i_;}; //ok
auto x5=[this]{return i_+x+y;}; //error,没有捕获x与y
auto x6=[this,x,y]{return i_+x+y;};//Ok
auto x7=[this]{return i_++} //ok
}
};
int a=0,b=1;
auto f1=[]{return a;}; //error没有捕获外部变量
auto f2=[&]{return a++;}; //ok
auto f3=[=]{return a;}; //ok
auto f4=[=]{return a++;}; //error, a是以复制方式捕获的无法修改
auto f5=[a]{return a+b;}; //error,没有捕获b变量
auto f6=[a,&b]{return a+(b++);};//ok
auto f7=[=,&b]{return a+(b++);};//ok
```
需要注意的是默认状态下lambda表达式无法修改通过复制方式捕获的外部变量如果希望修改需啊需要使用引用方式来捕获。
```
int a=0;
auto f=[=]{return a;};
a+=1;
std::cout<<f()<<std::endl;
```
在以上lambda表示式中修改外部变量并不会真正影响到外部我们却仍然无法修改他们。
那么如果希望去修改按值捕获的外部变量就需要显式致命表达式为mutable:
```
int a=0;
auto f1=[=]{return a++;}; //error
auto f2=[=]()mutable{return a++;}; //ok,mutable
```
需要注意的是被mutable修饰的lambda就算没有参数也需要写参数列表。
我们可以使用std::function与std::bind来操作lambda表达式另外对于没有捕获任何变量的lambda表达式还可以被转化为一个普通的函数指针
```
using func_t=int(*)(int);
func_t f=[](int a){return a;};
f(123);
```
lambda表示式可以基本代替std::function(一些老式库不支持lambda)
### 1.7 tupe元组
```
tuple<const char*,int>tp=make_tuple(sendPack,nSendSize);//构建一个tuple
```
使用tuple相当于构建一个对应的结构体<br>
可以使用std::tie来进行解包来获取对应的数值,也可以用来创建元组。
```
int x=1
int y=2;
string s="aa";
auto tp=std::tie(x,s,y);
//tp是实际类型是std::tuple<int&,string&,int&>
const char* data=tp.get<0>();
int len=tp.get<1>();
int x,y;
string a;
std::tie(x,a,y)=tp;
```
你可以使用std::ignore占位符来表示不解某个位置的值。
```
std::tie(std::ignore,std::ignorey)=tp;//只获取了第三个值
```

View File

@@ -0,0 +1,65 @@
### 7.1 委托构造函数和继承构造函数
#### 7.1.1 委托构造函数
委托构造函数允许在同一个类中一个构造函数可以调用另一个构造函数,从而可以在初始化时简化变量的初始化<br>
```
class class_c
{
public:
int max;
int min;
int middle;
class_c(int my_max) { max = my_max > 0 ? my_max : 10; }
class_c(int my_max, int my_min) :class_c(my_max) {
min = my_min > 0 && my_min < max ? my_min : 1;
}
class_c(int my_max, int my_min, int my_middle) :class_c(my_max, my_min) {
middle = my_middle<max&&my_middle>min ? my_middle : 5;
}
};
```
#### 7.1.2 继承构造函数
让派生类直接使用基类的构造函数
```
struct A:B
{
using B::B;
}
```
这个特性对普通函数也有效
### 7.2 原始字面量
```
string str="D:\A\B\test.test";
string str1="D:\\A\\B\\test.test";
string str2=R"()";
//输出
D:AB est.test
D:\A\B\test.test
D:\A\B\test.test
```
可以通过R直接得到原始意义的字符串只有()中的才有效果
### 7.3 final和override
final用来限制某个类不能被继承或者某个虚函数不能被重写
```
struct A{
virtual void foo() final;
}
struct B final:A{
}
```
override确保在派生类中声明的重写函数与基类的虚函数有相同的签名一样的形参防止写错同时也表明将要重写基类的虚函数还可以防止因疏忽把本来想重写积累的虚函数声明承载
### 7.4 内存对齐
#### 7.4.1 内存对齐
主要讲了一些概念以及msvc与gcc的内存对齐机制区别
http://blog.csdn.net/markl22222/article/details/38051483
我们经常会看到内存对齐的应用是在网络收发包中。一般用于发送的结构体都是1字节对齐的目的是统一收发双方可能处于不同平台之间的数据内存布局以及减少不必要的流量消耗。
#### 7.4.2 堆内存的内存对齐
msvc下使用_aligned_malloc使用默认的对齐大小32位是8字节64位是16字节当然也可以自己实现这里提供一个案例。
#### 7.4.3 利用alignas指定内存对齐大小
#### 7.4.4 利用alignof和std::alignment_of获取内存对齐大小
#### 7.4.5 内存对齐的类型std::aligned_storage
### 7.5 c++11新增便利算法

View File

@@ -0,0 +1,508 @@
## 使用c++11消除重复提高代码质量
### 3.1 type_traits———类型萃取
type_traits提供了丰富的编译期计算、查询、判断、转换和选择的帮助类在很多场合都会使用到。
### 3.1.1 基本的type_traits
##### 1.基本的type_traits<br>
无需自己定义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的移除与添加引用的修改————引用的移除与添加数组的修改和指针的修改。<br>
创建对象时需要去除引用:
```
template<typename T>
typnename std::remove_reference<t>::type* Create()
{
typedef typename std::remove_reference<t>::type U;
return new U();
}
```
模板参数T可能是引用类型而创建对象时需要原始的类型不能用引用类型所以需要将可能的引用引用。同样const也需要考虑进去。<br>
为了简洁可以使用std::decay,移除const与引用。<br>
另一个用处就是用来保存函数指针。
```
{
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来进行推断但是可读性较差。<br>
可以通过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来推断出最终值。<br>
当然可以用更加简洁的方法:
```
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后面带上省略号
省略号作用有两个:
1. 声明一个参数包这个参数包中可以包含0到任意个模板参数。
2. 在模板定义的右边,可以将参数包展开成一个一个独立参数。
### 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
```
1. 递归函数方式展开参数包
```
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;
}
```
2. 逗号表达式和初始化列表方式展开参数包
逗号运算符:<br>
运算符有左右两个表达式,先计算左侧的,之后丢弃结果再计算右侧。
```
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),}<br>
之后也可以使用std::initializer_list来优化这样就无需定义一个数组了使用lambda的话printarg函数就无需定义了。
### 3.2.2可变参数模板
std::tuple是一个可变参数模型
```
template<class... Type>
class tuple;
```
1. 模板递归和特化方式展开参数包
```
//前向声明
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)};
};
```
2. 继承方式展开参数包
有点复杂不做笔记了<br>
除了用继承还可以使用using来实现。
```
template<int N,int... Indexes>
struct MakeIndexes{
using type=MakeIndexes<N-1N-1Indexes...>::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函数需要解决
1. 函数的返回值可能是不同类型,如果以一种通用的返回值来消除这种不同返回值导致的差异
2. 函数的入参数目可能任意个数,且类型也不尽相同,如何来消除入参个数和类型的差异
```
#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;
}
```
后面若干章节较难,为了节约时间所以跳过了

View File

@@ -0,0 +1,210 @@
### 使用c++11开发一个半同步半异步线程池
```
#pragma once
#include<list>
#include<mutex>
#include<thread>
#include<condition_variable>
#include <iostream>
using namespace std;
template<typename T>
class SyncQueue
{
public:
SyncQueue(int maxSize) :m_maxSize(maxSize), m_needStop(false)
{
}
void Put(const T&x)
{
Add(x);
}
void Put(T&&x)
{
Add(std::forward<T>(x));
}
void Take(std::list<T>& list)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this] {return m_needStop || NotEmpty(); });
if (m_needStop)
return;
list = std::move(m_queue);
m_notFull.notify_one();
}
void Take(T& t)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this] {return m_needStop || NotEmpty(); });
if (m_needStop)
return;
t = m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
void Stop()
{
{
std::lock_guard<std::mutex> locker(m_mutex);
m_needStop = true;
}
m_notFull.notify_all();
m_notEmpty.notify_all();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
int Count()
{
return m_queue.size();
}
private:
bool NotFull() const
{
bool full = m_queue.size() >= m_maxSize;
if (full)
cout << "full, waitingthread id: " << this_thread::get_id() << endl;
return !full;
}
bool NotEmpty() const
{
bool empty = m_queue.empty();
if (empty)
cout << "empty,waitingthread id: " << this_thread::get_id() << endl;
return !empty;
}
template<typename F>
void Add(F&&x)
{
std::unique_lock< std::mutex> locker(m_mutex);
m_notFull.wait(locker, [this] {return m_needStop || NotFull(); });
if (m_needStop)
return;
m_queue.push_back(std::forward<F>(x));
m_notEmpty.notify_one();
}
private:
std::list<T> m_queue; //缓冲区
std::mutex m_mutex; //互斥量和条件变量结合起来使用
std::condition_variable m_notEmpty;//不为空的条件变量
std::condition_variable m_notFull; //没有满的条件变量
int m_maxSize; //同步队列最大的size
bool m_needStop; //停止的标志
};
```
```
#pragma once
#include<list>
#include<thread>
#include<functional>
#include<memory>
#include <atomic>
#include "SyncQueue.hpp"
const int MaxTaskCount = 100;
class ThreadPool
{
public:
using Task = std::function<void()>;
ThreadPool(int numThreads = std::thread::hardware_concurrency()) : m_queue(MaxTaskCount)
{
Start(numThreads);
}
~ThreadPool(void)
{
//如果没有停止时则主动停止线程池
Stop();
}
void Stop()
{
std::call_once(m_flag, [this]{StopThreadGroup(); }); //保证多线程情况下只调用一次StopThreadGroup
}
void AddTask(Task&&task)
{
m_queue.Put(std::forward<Task>(task));
}
void AddTask(const Task& task)
{
m_queue.Put(task);
}
private:
void Start(int numThreads)
{
m_running = true;
//创建线程组
for (int i = 0; i <numThreads; ++i)
{
m_threadgroup.push_back(std::make_shared<std::thread>(&ThreadPool::RunInThread, this));
}
}
void RunInThread()
{
while (m_running)
{
//取任务分别执行
std::list<Task> list;
m_queue.Take(list);
for (auto& task : list)
{
if (!m_running)
return;
task();
}
}
}
void StopThreadGroup()
{
m_queue.Stop(); //让同步队列中的线程停止
m_running = false; //置为false让内部线程跳出循环并退出
for (auto thread : m_threadgroup) //等待线程结束
{
if (thread)
thread->join();
}
m_threadgroup.clear();
}
std::list<std::shared_ptr<std::thread>> m_threadgroup; //处理任务的线程组
SyncQueue<Task> m_queue; //同步队列
atomic_bool m_running; //是否停止的标志
std::once_flag m_flag;
};
```

View File

@@ -0,0 +1,151 @@
## 使用c++改进出程序性能
### 2.1右值引用
左值:表达式结束后依然存在的持久对象</br>
右值:表达式结束时就不存在的临时对象</br>
一个区分左值与右值的便捷方法:看能不能对表达式取地址,如果能则为左值,否则为右值。所有的具名变量或者对象都未左值,而右值不具名。</br>
右值有两个概念构成,一个是将亡值,另一个是纯右值。
### 2.1.1 &&的特性
右值引用就是对一个右值进行引用的类型。因为右值不具名,所以我们只能通过引用的方式找到它。
```
template<typnename T>
void f(T&& param); //T的类型需要推断,所以是个universal references
template<typename T>
class Test{
Test(Test&& rhs); //已经定义类型,所以&&是个右值
};
void f(Test&& rhs); //同上
template<typnename T>
void f(const T&& params); //&&是个右值因为universal references仅仅在T&&下发生。
```
T&&被左值或者右值初始化被称为引用折叠。<br>
总结:
1. 左值和右值是独立于他们的类型的,右值引用类型可能是左值也可以是右值。
2. auto&&或是函数类型自动推导的T&&是一个未定的引用类型。
3. 所有的右值引用叠加到右值引用上仍然是右值引用其他都是左值引用。当T&&为函数模板时,输入左值,它会变成左值引用,而输入右值时则会变成具名的右值引用。
4. 编译器会将已经命名的右值引用视为左值,而将未命名的右值引用视为右值。
### 2.1.2 右值引用优化性能,避免深拷贝
对于含有堆内存的类,我们需要提供深拷贝的拷贝构造函数,如果使用默认构造函数,会导致堆内存的重复删除:
```
class A{
public:
A():m_ptr(new int(0))
{
}
~A()
{
delete m_ptr;
}
private:
int* m_ptr;
};
A Get(bool flag)
{
A a;
A b;
if(flag)
return a;
else
return b;
}
int main()
{
A a=Get(false); //运行出错
}
```
在上面的代码中默认构造函数是浅拷贝a和b会指向同一个指针m_ptr,在析构时会导致重复删除该指针,正确的方法是提供深拷贝函数。<br>
```
A(const A& a):m_ptr(new int(*a.m_ptr))
{
}
```
但是有时这种拷贝构造函数是没有必要的,使用移动构造函数会更好。<br>
```
A(A&& a):m_ptr(a.m_ptr)
{
a.m_ptr=nullptr;
}
```
这里的A&&用来根据参数是左值还是右值来建立分支,如果是临时值,则会选择移动构造函数。<br>
需要注意的是:一般在提供右值引用的构造函数同时,也会提供左值引用的拷贝构造函数,以保证移动不成还可以用拷贝;提供了移动也同时提供拷贝构造函数。<br>
有关拷贝构造函数相关的一些笔记:<br>
拷贝构造函数是C++独有的,它是一种特殊的构造函数,用基于同一类的一个对象构造和初始化另一个对象。
当没有重载拷贝构造函数时,通过默认拷贝构造函数来创建一个对象
A a;
A b(a);
A b=a; 都是拷贝构造函数来创建对象b
强调这里b对象是不存在的是用a 对象来构造和初始化b的
浅拷贝:如果复制的对象中引用了一个外部内容(例如分配在堆上的数据),那么在复制这个对象的时候,让新旧两个对象指向同一个外部内容,就是浅拷贝。(指针虽然复制了,但所指向的空间内容并没有复制,而是由两个对象共用,两个对象不独立,删除空间存在)<br>
深拷贝:如果在复制这个对象的时候为新对象制作了外部对象的独立复制,就是深拷贝。
拷贝构造函数重载声明如下:
```
A (const A&other)
```
下面为拷贝构造函数的实现:
```
class A
{
int m_i
A(const A& other):m_i(other.m_i)
{
Cout<<”拷贝构造函数”<<endl;
}
}
```
赋值函数<br>
当一个类的对象向该类的另一个对象赋值时,就会用到该类的赋值函数。
当没有重载赋值函数(赋值运算符)时,通过默认赋值函数来进行赋值操作
A a;
A b;
b=a; <br>
强调这里a,b对象是已经存在的是用a 对象来赋值给b的
赋值运算的重载声明如下:
```
A& operator = (const A& other)
```
1. 拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。
```
class A;
A a;
A b=a; //调用拷贝构造函数b不存在
A c(a) ; //调用拷贝构造函数
/****/
class A;
A a;
A b;
b = a ; //调用赋值函数(b存在)</span>
```
2. 一般来说在数据成员包含指针对象的时候,需要考虑两种不同的处理需求:一种是复制指针对象,另一种是引用指针对象。拷贝构造函数大多数情况下是复制,而赋值函数是引用对象
3. 实现不一样。拷贝构造函数首先是一个构造函数,它调用时候是通过参数的对象初始化产生一个对象。赋值函数则是把一个新的对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检察一下两个对象是不是同一个对象,如果是,不做任何操作,直接返回。
### 2.2 move语义
普通左值也可以借助移动语义进行优化<br>
c++11提供std::move方法将左值转化为右值从而方便使用移动语义。即强制将左值转化为右值引用。
```
{
std::list<std::string> tokens;
std::list<std::string> t=tokens;
}
std::list<std::string> tokens;
std::list<std::string> t=std::move(tokens);
```
事实上标准库容器都实现了move语义而一些int之类的基础类型计算使用move但仍然会发生拷贝。
### 2.3 forward和完美转发
右值引用类型独立于值,一个右值引用参数作为函数的形参,在函数内部再转发该参数时它已经变成一个左值了。<br>
所以我们需要“完美转发”是指在函数模板中完全依照模板的参数的类型即保持参数的左值、右值特征讲参数传递给函数模板中调用另一个函数。c++11中提供了std::forward
### 2.4emplace_back减少内存拷贝与移动
emplace_back能通过参数构造对象不需要拷贝或者移动内存相比push_back能够更好得避免内存的拷贝与移动使得容器性能得到进一步提升。大多数情况下应该优先使用emplace_back。<br>
其原理是直接利用构造函数直接构造对象<br>
当然在push_back还不能完全被代替比如在没有提供构造函数的的struct。

View File

@@ -0,0 +1,647 @@
## 使用c++11让线程线程开发变得简单
### 5.1 线程创建
1. jon()
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
t.join();//join将会阻塞当前线程直到线程执行结束
return 0;
}
```
2. detach
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
t.detach();//线程与对象分离,让线程作为后台线程去运行,但同时也无法与线程产生联系(控制)了
return 0;
}
```
线程不能被复制,但是可以被移动
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
std::thread t2(std::move(t));
t2.join();
return 0;
}
```
需要注意线程的生命周期
```
#include <thread>
void func(){}
int main()
{
std::thread t(func);
return 0;
}
```
以上代码会报错因为线程对象先于线程结束了可以通过join()或者detach()来解决,当然也可以放到一个容器中来保存,以保证线程生命周期
```
#include <thread>
std::vector<std::thread> g_list;
std::vector<std::shared_ptr<std::thread>> g_list2;
void func(){}
void CreateThread()
{
std::thread t(func);
g_list.push_back(std::move(t));
g_list2.push_back(std::make_shared<std::thread>(func));
}
int main()
{
void CreateThread();
for(auto& thread : g_list)
{
thread.join();
}
for(auto& thread : g_list2)
{
thread->join();
}
return 0;
}
```
#### 线程的基本用法
1. 获取当前信息
```
void func()
{
}
int main()
{
std::thread t(func);
std::cout << t.get_id() << std::endl;//当前线程id
//获得cpu核心数
std::cout << std::thread::hardware_concurrency() << std::endl;
}
```
2. 线程休眠
```
void f() {
std::this_thread::sleep_for(std::chrono::seconds(3));
std::cout << "time out" << std::endl;
}
int main()
{
std::thread t(f);
t.join();
}
```
### 互斥量
#### 独占互斥锁std::mutex
```
std::mutex g_lock;
void func() {
g_lock.lock();
std::cout << "entered thread" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "leaving thread" << std::this_thread::get_id() << std::endl;
g_lock.unlock();
}
int main()
{
std::thread t1(func);
std::thread t2(func);
std::thread t3(func);
t1.join();
t2.join();
t3.join();
}
```
使用lock_guard可以简化lock/unlock同时也更加安全。因为是在构造时自动锁定互斥量析构时自动解锁。
```
void func() {
std::lock_guard<std::mutex> locker(g_lock);
std::cout << "entered thread" << std::this_thread::get_id() << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "leaving thread" << std::this_thread::get_id() << std::endl;
}
```
#### 5.2.2 递归的独占互斥量std::recursive_mutex
递归锁允许同一个线程多次获得该锁,可以用来解决同一线程需要多次获取互斥量时死锁的问题。
```
struct Complex
{
std::mutex mutex;
int i;
Complex() :i(0) {}
void mul(int x) {
std::lock_guard<std::mutex> lock(mutex);
i *= x;
}
void div(int x) {
std::lock_guard<std::mutex> lock(mutex);
i *= x;
}
void both(int x,int y) {
std::lock_guard<std::mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(32, 23);
}
```
简单的方法就是使用std::recursive_mutex
```
struct Complex
{
std::recursive_mutex mutex;
int i;
Complex() :i(0) {}
void mul(int x) {
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void div(int x) {
std::lock_guard<std::recursive_mutex> lock(mutex);
i *= x;
}
void both(int x,int y) {
std::lock_guard<std::recursive_mutex> lock(mutex);
mul(x);
div(y);
}
};
int main()
{
Complex complex;
complex.both(32, 23);
}
```
需要注意的是:
1. 需要用到递归锁定的多线程互斥处理往往本身就可以简化,允许递归互斥很容易放纵复杂逻辑的产生
2. 递归锁比起非递归锁效率会低一些
3. 递归锁虽然允许同一个线程多次获得同一个互斥锁可重复获得的最大次数并未具体说明一旦超过一定次数再对lock进行调用就会抛出std::system错误
#### 5.2.3带超时的互斥量std::timed_mutex和std::recursive_timed_mutex
主要用于在获取锁时增加超时等待功能。不至于一直等待
```
std::timed_mutex mutex;
void work()
{
std::chrono::milliseconds timeout(100);
while (true)
{
if (mutex.try_lock_for(timeout))
{
std::cout << std::this_thread::get_id() << ": do work with the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(250);
std::this_thread::sleep_for(sleepDuration);
mutex.unlock();
std::this_thread::sleep_for(sleepDuration);
}
else
{
std::cout << std::this_thread::get_id() << ": do work without the mutex" << std::endl;
std::chrono::milliseconds sleepDuration(100);
std::this_thread::sleep_for(sleepDuration);
}
}
}
int main(void)
{
std::thread t1(work);
std::thread t2(work);
t1.join();
t2.join();
return 0;
}
```
#### 5.3 条件变量
条件变量时c++11提供的另外一种用于等待的同步机制它能柱塞一个或者多个线程直到收到另一个线程发出的通知或者超时才会唤醒当前阻塞的线程。<br>
condition_variable
condition_variable_any
```
#include<list>
#include<mutex>
#include<thread>
#include<condition_variable>
#include <iostream>
template<typename T>
class SyncQueue
{
private:
bool IsFull() const
{
return m_queue.size() == m_maxSize;
}
bool IsEmpty() const
{
return m_queue.empty();
}
public:
SyncQueue(int maxSize) : m_maxSize(maxSize)
{
}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsFull())
{
std::cout << "缓冲区满了,需要等待..." << std::endl;
m_notFull.wait(m_mutex);
}
m_queue.push_back(x);
m_notFull.notify_one();
}
void Take(T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
while (IsEmpty())
{
std::cout << "缓冲区空了,需要等待..." << std::endl;
m_notEmpty.wait(m_mutex);
}
x = m_queue.front();
m_queue.pop_front();
m_notFull.notify_one();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
bool Full()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size() == m_maxSize;
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
int Count()
{
return m_queue.size();
}
private:
std::list<T> m_queue; //缓冲区
std::mutex m_mutex; //互斥量和条件变量结合起来使用
std::condition_variable_any m_notEmpty;//不为空的条件变量
std::condition_variable_any m_notFull; //没有满的条件变量
int m_maxSize; //同步队列最大的size
};
```
更好的方法是使用unique_lockunique可以随时释放锁
```
#include <thread>
#include <condition_variable>
#include <mutex>
#include <list>
#include <iostream>
template<typename T>
class SimpleSyncQueue
{
public:
SimpleSyncQueue(){}
void Put(const T& x)
{
std::lock_guard<std::mutex> locker(m_mutex);
m_queue.push_back(x);
m_notEmpty.notify_one();
}
void Take(T& x)
{
std::unique_lock<std::mutex> locker(m_mutex);
m_notEmpty.wait(locker, [this]{return !m_queue.empty(); });
x = m_queue.front();
m_queue.pop_front();
}
bool Empty()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.empty();
}
size_t Size()
{
std::lock_guard<std::mutex> locker(m_mutex);
return m_queue.size();
}
private:
std::list<T> m_queue;
std::mutex m_mutex;
std::condition_variable m_notEmpty;
};
```
#### 5.4 原子变量
使用原子变量就不需要来使用互斥量来保护该变量了<br>
使用std::mutex
```
#include <iostream>
#include <thread>
#include <mutex>
struct Counter
{
int value = 0;
std::mutex mutex;
void increment()
{
std::lock_guard<std::mutex> lock(mutex);
++value;
std::cout << value << std::endl;
}
void decrement()
{
std::lock_guard<std::mutex> lock(mutex);
--value;
std::cout << value << std::endl;
}
int get()
{
return value;
}
};
Counter g_counter;
void Increments()
{
for (int i = 0; i < 10; ++i)
{
g_counter.increment();
}
}
void Decrements()
{
for (int i = 0; i < 5; ++i)
{
g_counter.decrement();
}
}
int main(void)
{
std::thread t1(Increments);
std::thread t2(Decrements);
t1.join();
t2.join();
system("pause");
return 0;
}
```
使用std::atomic<T>
```
#include <iostream>
#include <thread>
#include <atomic>
struct Counter
{
std::atomic<int> value = 0;
void increment()
{
++value;
}
void decrement()
{
--value;
}
int get()
{
return value;
}
};
Counter g_counter;
void Increments()
{
for (int i = 0; i < 10; ++i)
{
g_counter.increment();
std::cout << g_counter.get() << std::endl;
}
}
void Decrements()
{
for (int i = 0; i < 5; ++i)
{
g_counter.decrement();
std::cout << g_counter.get() << std::endl;
}
}
int main(void)
{
std::thread t1(Increments);
std::thread t2(Decrements);
t1.join();
t2.join();
system("pause");
return 0;
}
```
#### 5.5 call_once/once_flag的使用
为了保证多线程环境中某个函数仅被调用一次,比如需要初始化某个对象,而这个对象只能初始化一次。
```
#include <thread>
#include <iostream>
#include <mutex>
std::once_flag flag;
void do_once() {
std::call_once(flag, [] {std::cout << "Called once" << std::endl; });
}
int main(void)
{
std::thread t1(do_once);
std::thread t2(do_once);
std::thread t3(do_once);
t1.join();
t2.join();
t3.join();
system("pause");
return 0;
}
```
#### 5.6 异步操作类
#### 5.6.1 std::future
thread库提供了future用来访问异步操作结果因为异步操作结果是一个未来的期待值所以被称为future。future提供了异步获取操作结果的通道我们可以以同步等待的方式来获取结果可以通过查询future的状态(future_status)来获取异步操作结果。
```
std::future_status status;
do
{
status = future.wait_for(std::chromno::seconds(1));
if (status == std::future_status::deferred) {
}
else if (status == std::future_status::timeout) {
}
else if (status==std::future_status::ready) {
}
} while (status!= std::future_status::ready);
```
获取future有三种方式:
1. get等待异步操作结束并返回结果
2. wait等待异步操作完成
3. wait_for超时等待返回结果
#### 5.6.2 std::promise
std::promise将数据和future绑定起来为获取线程函数中的某个值提供便利在线程函数中为外面传进来的promise赋值在线程函数执行完成之后就可以通过promise的future获取该值。
```
std::promise<int> pr;
std::thread t([](std::promise<int>&p) {
p.set_value_at_thread_exit(9);
}, std::ref(pr));
std::future<int> f = pr.get_future();
auto r = f.get();
```
#### 5.6.3 std::package_task
```
std::packaged_task<int()> task([]() {return 7; });
std::thread t1(std::ref(task));
std::future<int> f1 = task.get_future();
auto r1 = f1.get();
```
#### 5.6.4 以上三者关系
std::future提供了一个访问异步操作结果的机制它和线程是一个级别的属于低层次对象。之上是std::packaged_task和std::promise他们内部都有future以便访问异步操作结果std::packaged_task包装的是一个异步操作std::promise包装的是一个值。那这两者又是什么关系呢可以将一个异步操作的结果放到std::promise中。<br>
future被promise和package_task用来作为异步操作或者异步的结果的连接通道用std::future和std::shared_future来获取异步的调用结果。future是不可拷贝的shared_future是可以拷贝的当需要将future放到容器中则需要shared_future。
```
#include <iostream>
#include <thread>
#include <utility>
#include <future>
#include <vector>
int func(int x) { return x + 2; }
int main(void)
{
std::packaged_task<int(int)> tsk(func);
std::future<int> fut = tsk.get_future(); //获取future
std::thread(std::move(tsk), 2).detach();
int value = fut.get(); //等待task完成并获取返回值
std::cout << "The result is " << value << ".\n";
std::vector<std::shared_future<int>> v;
std::shared_future<int> f = std::async(std::launch::async, [](int a, int b){return a + b; }, 2, 3);
v.push_back(f);
std::cout << "The shared_future result is " << v[0].get() << std::endl;
return 0;
}
```
#### 5.7 线程异步操作函数
std::async可以直接创建异步的task异步任务结果也保存在future中调用furturn.get()获取即可如果不关心结果可以使用furturn.wait()<br>
两种线程创建策略:<br>
1. std::launch::async:在调用async时就开始创建线程。
2. std::launch::deferred:延迟加载方式创建线程。调用async时不创建线程直到调用future的get或者wait时才创建。
```
#include <iostream>
#include <future>
void TestAsync()
{
std::future<int> f1 = std::async(std::launch::async, [](){
return 8;
});
std::cout << f1.get() << std::endl; //output: 8
std::future<void> f2 = std::async(std::launch::async, [](){
std::cout << 8 << std::endl; return;
});
f2.wait(); //output: 8
std::future<int> future = std::async(std::launch::async, [](){
std::this_thread::sleep_for(std::chrono::seconds(3));
return 8;
});
std::cout << "waiting...\n";
std::future_status status;
do {
status = future.wait_for(std::chrono::seconds(1));
if (status == std::future_status::deferred)
{
std::cout << "deferred\n";
}
else if (status == std::future_status::timeout)
{
std::cout << "timeout\n";
}
else if (status == std::future_status::ready)
{
std::cout << "ready!\n";
}
} while (status != std::future_status::ready);
std::cout << "result is " << future.get() << '\n';
}
int main(void)
{
TestAsync();
return 0;
}
```

View File

@@ -0,0 +1,249 @@
### 使用c++11改进我们的模式
### 8.1 改进单例模式
单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。
```
#pragma once
template <typename T>
class Singleton
{
public:
template<typename... Args>
  static T* Instance(Args&&... args)
  {
if(m_pInstance==nullptr)
m_pInstance = new T(std::forward<Args>(args)...);
return m_pInstance;
}
  static T* GetInstance()
  {
    if (m_pInstance == nullptr)
      throw std::logic_error("the instance is not init, please initialize the instance first");
    return m_pInstance;
  }
static void DestroyInstance()
{
delete m_pInstance;
m_pInstance = nullptr;
}
private:
Singleton(void);
virtual ~Singleton(void);
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
private:
static T* m_pInstance;
};
template <class T> T* Singleton<T>::m_pInstance = nullptr;
```
使用了可变参数与完美转发避免了写N个模板函数
单例模式应用的场景一般发现在以下条件下:
1. 资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
2. 控制资源的情况下,方便资源之间的互相通信。如线程池等。
3. 只需一个存在就可以的情况
### 8.2 改进观察者模式
观察者模式定义对象间的一种一对多的依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都能得到通知并被自动更新。
```
#pragma once
class NonCopyable
{
public:
NonCopyable(const NonCopyable&) = delete; // deleted
NonCopyable& operator = (const NonCopyable&) = delete; // deleted
NonCopyable() = default; // available
};
```
```
#include <iostream>
#include <string>
#include <functional>
#include <map>
using namespace std;
#include "NonCopyable.hpp"
template<typename Func>
class Events : NonCopyable
{
public:
//注册观察者,支持右值引用
int Connect(Func&& f)
{
return Assgin(f);
}
//注册观察者
int Connect(const Func& f)
{
return Assgin(f);
}
//移除观察者
void Disconnect(int key)
{
m_connections.erase(key);
}
//通知所有的观察者
template<typename... Args>
void Notify(Args&&... args)
{
for (auto& it: m_connections)
{
it.second(std::forward<Args>(args)...);
}
}
private:
//保存观察者并分配观察者的编号
template<typename F>
int Assgin(F&& f)
{
int k=m_observerId++;
m_connections.emplace(k, std::forward<F>(f));
return k;
}
int m_observerId=0;//观察者对应的编号
std::map<int, Func> m_connections;//观察者列表
};
```
### 8.3 改进访问者模式
访问者模式需要注意的问题:定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重新定义对所有访问者的接口。<br>
通过可变参数模板就可以实现一个稳定的接口层,可以让访问者的接口层访问任意个数的访问者,这样就不需要每增加一个新的被访问者就修改接口层,从而使接口层保证稳定。
```
template<typename... Types>
struct Visitor;
template<typename T, typename... Types>
struct Visitor<T, Types...> : Visitor<Types...>
{
using Visitor<Types...>::Visit;//通过using避免隐藏基类的visit同名方法
virtual void Visit(const T&) = 0;
};
template<typename T>
struct Visitor<T>
{
virtual void Visit(const T&) = 0;
};
```
### 8.4 改进命令模式
命令模式的作用是将请求封装成一个对象,将请求的发起者和执行者解耦,支持对请求排队、撤销和重做。由于将请求都封装成一个一个命令对象了,使得我们可以集中处理或者延迟处理这些命令请求,而且不同客户对象可以共享这些命令,还可以控制请求的优先权、排队、支持请求的撤销和重做。<br>
缺点:越来越多的命令会导致类爆炸,难以管理<br>
接收function、函数对象、lambda和普通函数的包装器
```
template<class F,class... Args,class=typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Wrap(F&& f, Args && ...args)
{
return f(std::forward<Args>(args)...);
}
```
接收成员函数的包装器:
```
template<class R,class C,class... DArgs,class P,class... Args>
void Wrap(R(C::*f)(DArgs...),P&& p,Args&& ... args)
{
return; (*p.*f)(std::forward<Args>(args)...);
}
```
```
#include <functional>
#include <type_traits>
template<typename Ret = void>
struct CommCommand
{
private:
std::function < Ret()> m_f;
public:
//接受可调用对象的函数包装器
template< class F, class... Args, class = typename std::enable_if<!std::is_member_function_pointer<F>::value>::type>
void Wrap(F && f, Args && ... args)
{
m_f = [&]{return f(args...); };
}
//接受常量成员函数的函数包装器
template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...) const, P && p, Args && ... args)
{
m_f = [&, f]{return (*p.*f)(args...); };
}
//接受非常量成员函数的函数包装器
template<class R, class C, class... DArgs, class P, class... Args>
void Wrap(R(C::*f)(DArgs...), P && p, Args && ... args)
{
m_f = [&, f]{return (*p.*f)(args...); };
}
Ret Excecute()
{
return m_f();
}
};
```
### 8.5 改进对象池
```
#include <string>
#include <functional>
#include <memory>
#include <map>
#include "NonCopyable.hpp"
using namespace std;
const int MaxObjectNum = 10;
template<typename T>
class ObjectPool : NonCopyable
{
template<typename... Args>
using Constructor = std::function<std::shared_ptr<T>(Args...)>;
public:
//默认创建多少个对象
template<typename... Args>
void Init(size_t num, Args&&... args)
{
if (num<= 0 || num> MaxObjectNum)
throw std::logic_error("object num out of range.");
auto constructName = typeid(Constructor<Args...>).name(); //不区分引用
for (size_t i = 0; i <num; i++)
{
m_object_map.emplace(constructName, shared_ptr<T>(new T(std::forward<Args>(args)...), [this, constructName](T* p) //删除器中不直接删除对象,而是回收到对象池中,以供下次使用
{
m_object_map.emplace(std::move(constructName), std::shared_ptr<T>(p));
}));
}
}
//从对象池中获取一个对象
template<typename... Args>
std::shared_ptr<T> Get()
{
string constructName = typeid(Constructor<Args...>).name();
auto range = m_object_map.equal_range(constructName);
for (auto it = range.first; it != range.second; ++it)
{
auto ptr = it->second;
m_object_map.erase(it);
return ptr;
}
return nullptr;
}
private:
multimap<string, std::shared_ptr<T>> m_object_map;
};
```

View File

@@ -0,0 +1,120 @@
### 使用c++11中便利的工具
#### 6.1 处理日期和时间的chrono库
#### 6.1.1 记录时长的duration
```
std::chrono::duration <rep,std::ratio<1,1>> seconds;//表示秒
```
chrono的count(),可以获取时钟周期数<br>
需要注意的是当两个duration时钟周期不同的时候会先统一周期再进行计算。<br>
还可以通过duration_cast<>()进行时钟周期的转换
#### 6.1.2 表示时间点的time point
#### 6.1.3 获得系统时钟的clocks
#### 6.1.4 计时器timer
```
#include<chrono>
class Timer
{
public:
Timer() : m_begin(std::chrono::high_resolution_clock::now()) {}
void reset()
{
m_begin = std::chrono::high_resolution_clock::now();
}
//默认输出毫秒
int64_t elapsed() const
{
return std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//微秒
int64_t elapsed_micro() const
{
return std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//纳秒
int64_t elapsed_nano() const
{
return std::chrono::duration_cast<std::chrono::nanoseconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//秒
int64_t elapsed_seconds() const
{
return std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//分
int64_t elapsed_minutes() const
{
return std::chrono::duration_cast<std::chrono::minutes>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
//时
int64_t elapsed_hours() const
{
return std::chrono::duration_cast<std::chrono::hours>(
std::chrono::high_resolution_clock::now() - m_begin).count();
}
private:
std::chrono::time_point<std::chrono::high_resolution_clock> m_begin;
};
void fun()
{
std::cout << "hello world" << std::endl;
}
void Test()
{
std::cout << "\nTest()\n";
Timer t; //开始计时
fun();
std::cout << t.elapsed_seconds() << std::endl; //打印fun函数耗时多少秒
std::cout << t.elapsed_nano() << std::endl; //打印纳秒
std::cout << t.elapsed_micro() << std::endl; //打印微秒
std::cout << t.elapsed() << std::endl; //打印毫秒
std::cout << t.elapsed_seconds() << std::endl; //打印秒
std::cout << t.elapsed_minutes() << std::endl; //打印分钟
std::cout << t.elapsed_hours() << std::endl; //打印小时
}
int main(void)
{
Test();
system("pause");
return 0;
}
```
#### 6.2 数值类型和字符类型的互相转换
std::string to_string(type value)
std::string to_wstring(type value)
1. atoi字符串转int
2. atol字符串转long
3. atoll字符串转long long
4. atof字符串转float
#### 6.3 宽窄字符转换
```
std::wstring str=L"中国人";
//宽窄字符转换器
std::codecvt_utf8
std::codecvt_utf16
std::codecvt_utf8_utf16
std::wstring_convert
```

View File

@@ -0,0 +1,121 @@
### 使用c++11开发一个轻量级的Ioc容器
Ioc容器具备两种能力一种是对象工厂不仅可以创建所有的对象还可以根据配置去创建对象另一种能力是可以去创建依赖对象应有不需要直接创建对象由Ioc容器去创建实现控制反转。<br>
实现Ioc容器需要解决三个问题第一个问题是创建所有类型的对象第二个问题是类型擦除第三个问题是如何创建依赖对象。
#### 类型擦除的常用方法
类型擦除就是讲原有类型消除或者隐藏。常用的方法有:
1. 通过多态来擦除类型
2. 通过模板来擦除类型
3. 通过某种类型的模板容器擦除类型(Variant)
4. 通过某种通用类型来擦除类型(any)
5. 通过闭包来擦除类型通过模板函数将数值包入函数中再包入std::function
```
#pragma once
#include<string>
#include<unordered_map>
#include<memory>
#include<functional>
using namespace std;
#include<Any.hpp>
#include <NonCopyable.hpp>
class IocContainer : NonCopyable
{
public:
IocContainer(void){}
~IocContainer(void){}
template<class T, typename Depend, typename... Args>
void RegisterType(const string& strKey)
{
std::function<T* (Args...)> function = [](Args... args){ return new T(new Depend(args...)); };//通过闭包擦除了参数类型
RegisterType(strKey, function);
}
template<class T, typename... Args>
T* Resolve(const string& strKey, Args... args)
{
if (m_creatorMap.find(strKey) == m_creatorMap.end())
returnnullptr;
Any resolver = m_creatorMap[strKey];
std::function<T* (Args...)> function = resolver.AnyCast<std::function<T* (Args...)>>();
return function(args...);
}
template<class T, typename... Args>
std::shared_ptr<T> ResolveShared(const string& strKey, Args... args)
{
T* t = Resolve<T>(strKey, args...);
return std::shared_ptr<T>(t);
}
private:
void RegisterType(const string& strKey, Any constructor)
{
if (m_creatorMap.find(strKey) != m_creatorMap.end())
throw std::invalid_argument("this key has already exist!");
//通过Any擦除了不同类型的构造器
m_creatorMap.emplace(strKey, constructor);
}
private:
unordered_map<string, Any> m_creatorMap;
};
/*test code
struct Base
{
virtual void Func(){}
virtual ~Base(){}
};
struct DerivedB : public Base
{
DerivedB(int a, double b):m_a(a),m_b(b)
{
}
void Func()override
{
cout<<m_a+m_b<<endl;
}
private:
int m_a;
double m_b;
};
struct DerivedC : public Base
{
};
struct A
{
A(Base * ptr) :m_ptr(ptr)
{
}
void Func()
{
m_ptr->Func();
}
~A()
{
if(m_ptr!=nullptr)
{
delete m_ptr;
m_ptr = nullptr;
}
}
private:
Base * m_ptr;
};
void TestIoc()
{
IocContainer ioc;
ioc.RegisterType<A, DerivedC>(“C”); //配置依赖关系
auto c = ioc.ResolveShared<A>(“C”);
ioc.RegisterType<A, DerivedB, int, double>(“C”); //注册时要注意DerivedB的参数int和double
auto b = ioc.ResolveShared<A>(“C”, 1, 2.0); //还要传入参数
b->Func();
}
*/
```

View File

@@ -0,0 +1,235 @@
### 使用c++11封装sqlite库
#### 13.1 sqlite基本用法
对于带参数的sql语句<br>
其中sqlite3_perpare_v2用于解析sql文本并且保存到sqlite_stmt对象中sqlite3_stmt将作为后面一些函数的入参sqlite3_bind_XXX用于绑定sql文本中的参数
```
#include <sqlite3.h>
#include <string>
bool test()
{
sqlite3* dbHandle = nullptr;
int result = sqlite3_open("test.db", &dbHandle);
if (result != SQLITE_OK)
{
sqlite3_close(dbHandle);
return false;
}
const char* sqlcreat = "CREATE TABLE if not exists PersonTable(ID INTEGER NOT NULL, Name Text,Address BLOB);";
result = sqlite3_exec(dbHandle, sqlcreat, nullptr, nullptr, nullptr);
//插入数据
sqlite3_stmt* stmt = NULL;
const char* sqlinsert = "INSERT INTO PersonTable(ID,Name,Adress) VALUE(?,?,?);";
//解析并且保存sql脚本
sqlite3_prepare_v2(dbHandle, sqlinsert, strlen(sqlinsert), &stmt, nullptr);
int id = 2;
const char* name = "peter";
for (int i=0;i<10;++i)
{
sqlite3_bind_int(stmt, 1, id);
sqlite3_bind_text(stmt, 2, name, strlen(name), SQLITE_TRANSIENT);
sqlite3_bind_null(stmt, 3);
if (sqlite3_step(stmt) != SQLITE_DONE)
{
sqlite3_finalize(stmt);
sqlite3_close(dbHandle);
}
//重新初始化stmt对象下次再用
sqlite3_reset(stmt);
}
//使用完需要释放,不然会内存泄露
sqlite3_finalize(stmt);
sqlite3_close(dbHandle);
return result = SQLITE_OK;
}
```
最后通过
sqlite3_colume_xxx(sqlite3_stmt*,int iCol)取得结果
```
int colCount=sqlite3_column_count(stmt);
while(true)
{
int r=sqlite3_step(stmt);
if(r==SQLITE_DONE)
{
break;//数据行都已经获取,跳出循环
}
if(r==SQLITE_ROW)
{
break;//获得某一行数据失败,跳出循环
}
//获得每一列数据
for(int i=0;i<colCount;++i)
{
int coltype=sqlite3_column_type(stmt,i);
if(coltype==SQLITE_INTEGER)
{
int val=sqlite3_column_int(stmt,i);
}else if(coltype==SQLITE_FLOAT)
{
double val=sqlite3_column_double(stmt,i);
}else if(coltype==SQLITE_TEXT)
{
const char* val=(const char*)sqlite3_column_text(stmt,i);
}else if(coltype==SQLITE_NULL)
{
}
}
}
sqlite3_finalize(stmt);
```
事务
```
sqlite3_exec(dbHandle,"BEGIN");
sqlite3_exec(dbHandle,"ROLLBACK");
sqlite3_exec(dbHandle,"COMMIT");
sqlite3_exec(dbHandle,"END");
```
```
/**
* 不带占位符。执行sql不带返回结果, 如insert,update,delete等
* @param[in] query: sql语句, 不带占位符
* @return bool, 成功返回true否则返回false
*/
bool Excecute(const string& sqlStr)
{
m_code = sqlite3_exec(m_dbHandle, sqlStr.data(), nullptr, nullptr, nullptr);
return SQLITE_OK == m_code;
}
/**
* 带占位符。执行sql不带返回结果, 如insert,update,delete等
* @param[in] query: sql语句, 可能带占位符"?"
* @param[in] args: 参数列表,用来填充占位符
* @return bool, 成功返回true否则返回false
*/
template <typename... Args>
bool Excecute(const string& sqlStr, Args && ... args)
{
if (!Prepare(sqlStr))
{
return false;
}
return ExcecuteArgs(std::forward<Args>(args)...);
}
/**
* 批量操作之前准备sql接口必须和ExcecuteBulk一起调用准备批量操作的sql可能带占位符
* @param[in] query: sql语句, 带占位符"?"
* @return bool, 成功返回true否则返回false
*/
bool Prepare(const string& sqlStr)
{
m_code = sqlite3_prepare_v2(m_dbHandle, sqlStr.data(), -1, &m_statement, nullptr);
if (m_code != SQLITE_OK)
{
return false;
}
return true;
}
/**
* 批量操作接口必须先调用Prepare接口
* @param[in] args: 参数列表
* @return bool, 成功返回true否则返回false
*/
template <typename... Args>
bool ExcecuteArgs(Args && ... args)
{
if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...))
{
return false;
}
m_code = sqlite3_step(m_statement);
sqlite3_reset(m_statement);
return m_code == SQLITE_DONE;
}
template<typename Tuple>
bool ExcecuteTuple(const string& sqlStr, Tuple&& t)
{
if (!Prepare(sqlStr))
{
return false;
}
m_code = detail::ExcecuteTuple(m_statement, detail::MakeIndexes<std::tuple_size<Tuple>::value>::type(), std::forward<Tuple>(t));
return m_code == SQLITE_DONE;
}
bool ExcecuteJson(const string& sqlStr, const char* json)
{
rapidjson::Document doc;
doc.Parse<0>(json);
if (doc.HasParseError())
{
cout << doc.GetParseError() << endl;
return false;
}
if (!Prepare(sqlStr))
{
return false;
}
return JsonTransaction(doc);
}
/**
* 执行sql返回函数执行的一个值, 执行简单的汇聚函数如select count(*), select max(*)等
* 返回结果可能有多种类型返回Value类型在外面通过get函数去取
* @param[in] query: sql语句, 可能带占位符"?"
* @param[in] args: 参数列表,用来填充占位符
* @return int: 返回结果值,失败则返回-1
*/
template < typename R = sqlite_int64, typename... Args>
R ExecuteScalar(const string& sqlStr, Args&&... args)
{
if (!Prepare(sqlStr))
return GetErrorVal<R>();
if (SQLITE_OK != detail::BindParams(m_statement, 1, std::forward<Args>(args)...))
{
return GetErrorVal<R>();
}
m_code = sqlite3_step(m_statement);
if (m_code != SQLITE_ROW)
return GetErrorVal<R>();
SqliteValue val = GetValue(m_statement, 0);
R result = val.Get<R>();// get<R>(val);
sqlite3_reset(m_statement);
return result;
}
template <typename... Args>
std::shared_ptr<rapidjson::Document> Query(const string& query, Args&&... args)
{
if (!PrepareStatement(query, std::forward<Args>(args)...))
nullptr;
auto doc = std::make_shared<rapidjson::Document>();
m_buf.Clear();
m_jsonHelper.BuildJsonObject(m_statement);
doc->Parse<0>(m_buf.GetString());
return doc;
}
```
详细代码见
https://github.com/qicosmos/SmartDB1.03/blob/master/SmartDB.hpp

View File

@@ -0,0 +1,2 @@
### 使用c++11开发一个对象的消息总线库
大致了解消息总线的设计思路,但是没有一定的模板基础,代码看了也白看

View File

@@ -0,0 +1,132 @@
### 使用c++11开发一个轻量级的并行task库
#### 15.1 TBB的基本用法
#### 15.1.1 TBB概述
TBB 是inter用标准c++写的一个开源的并行计算库,它的目的是提升数据并行计算的能力。主要功能如下:
1. 并行计算
2. 任务调度
3. 并行容器
4. 同步原语
5. 内存分配器
#### 15.1.2 TBB并行算法
1. parallel_for以并行的方式遍历一个区间
2. parallel_do和parallel_for_each将算法用于一个区间
3. parallel_reduce并行汇聚
4. parallel_pipeline并行的管道过滤器
5. parallel_sort和parallel_invoke并行排序和调和
#### 15.1.3 TBB的任务组
```
tbb::task_group g;
g.run([]{task();});
g.run([]{task();});
g.run([]{task();});
g.wait();
```
#### 15.2 PPL的基本用法
两者差异:
1. parallel_reduce的原型有些不同。
2. PPL中没有parallel_pipeline接口
3. TBB的task没有PPL的task强大PPL的task可以链式连续执行还可以组合任务而TBB的task不行。
#### 15.5 TaskCpp的任务
#### 15.5.1 task的实现
基于task的并行编程模型最基本的执行单元是task一个task就代表了一个要执行的任务。外部只需要简单调用接口就可以创建task并且执行另一个细节就是异步执行。
```
template<typename T>
class Task;
template<typename R, typename...Args>
class Task<R(Args...)>
{
std::function<R(Args...)> m_fn;
public:
typedef R return_type;
template<typename F>
auto Then(F&& f)//->Task<typename std::result_of<F(R)>::type(Args...)>
{
typedef typename std::result_of<F(R)>::type ReturnType;
auto func = std::move(m_fn);
return Task<ReturnType(Args...)>([func, &f](Args&&... args)
{
std::future<R> lastf = std::async(func, std::forward<Args>(args)...);
return std::async(f, lastf.get()).get();
});
}
Task(std::function<R(Args...)>&& f) :m_fn(std::move(f)){}
Task(std::function<R(Args...)>& f) :m_fn(f){}
~Task()
{
}
void Wait()
{
std::async(m_fn).wait();
}
template<typename... Args>
R Get(Args&&... args)
{
return std::async(m_fn, std::forward<Args>(args)...).get();
}
std::shared_future<R> Run()
{
return std::async(m_fn);
}
};
```
#### 15.5.2 task的延续
```
#include <functional>
namespace Cosmos
{
template<typename T>
class Task;
template<typename R, typename...Args>
class Task<R(Args...)>
{
std::function<R(Args...)> m_fn;
public:
typedef R return_type;
template<typename F>
auto Then(F&& f)//->Task<typename std::result_of<F(R)>::type(Args...)>
{
typedef typename std::result_of<F(R)>::type ReturnType;
auto func = std::move(m_fn);
return Task<ReturnType(Args...)>([func, &f](Args&&... args)
{
std::future<R> lastf = std::async(func, std::forward<Args>(args)...);
return std::async(f, lastf.get()).get();
});
}
Task(std::function<R(Args...)>&& f) :m_fn(std::move(f)){}
Task(std::function<R(Args...)>& f) :m_fn(f){}
~Task()
{
}
void Wait()
{
std::async(m_fn).wait();
}
template<typename... Args>
R Get(Args&&... args)
{
return std::async(m_fn, std::forward<Args>(args)...).get();
}
std::shared_future<R> Run()
{
return std::async(m_fn);
}
};
}
```

View File

@@ -0,0 +1 @@
所有类型的容器和数组都抽象为一个Range这个Range由一族迭代器组成然后就可以基于这个抽象的Range实现更为抽象、规范、统一的算法了。Boost库已经实现了Range。

View File

@@ -0,0 +1,109 @@
### 使用c++11开发一个轻量级的AOP库
AOP把软件系统分为两个部分核心关注点和横切关注点。业务处理的主要流程是核心关注点与之关系不大的部分是横切关注点。横切关注点的一个特点是他们经常发生在核心关注点的多处而各处基本相似比如权限认证、日志、事务处理
```
#pragma once
#define HAS_MEMBER(member)\
template<typename T, typename... Args>struct has_member_##member\
{\
private:\
template<typename U> static auto Check(int) -> decltype(std::declval<U>().member(std::declval<Args>()...), std::true_type()); \
template<typename U> static std::false_type Check(...);\
public:\
enum{value = std::is_same<decltype(Check<T>(0)), std::true_type>::value};\
};\
HAS_MEMBER(Foo)
HAS_MEMBER(Before)
HAS_MEMBER(After)
#include <NonCopyable.hpp>
template<typename Func, typename... Args>
struct Aspect : NonCopyable
{
Aspect(Func&& f) : m_func(std::forward<Func>(f))
{
}
template<typename T>
typename std::enable_if<has_member_Before<T, Args...>::value&&has_member_After<T, Args...>::value>::type Invoke(Args&&... args, T&& aspect)
{
aspect.Before(std::forward<Args>(args)...);//核心逻辑之前的切面逻辑
m_func(std::forward<Args>(args)...);//核心逻辑
aspect.After(std::forward<Args>(args)...);//核心逻辑之后的切面逻辑
}
template<typename T>
typename std::enable_if<has_member_Before<T, Args...>::value&&!has_member_After<T, Args...>::value>::type Invoke(Args&&... args, T&& aspect)
{
aspect.Before(std::forward<Args>(args)...);//核心逻辑之前的切面逻辑
m_func(std::forward<Args>(args)...);//核心逻辑
}
template<typename T>
typename std::enable_if<!has_member_Before<T, Args...>::value&&has_member_After<T, Args...>::value>::type Invoke(Args&&... args, T&& aspect)
{
m_func(std::forward<Args>(args)...);//核心逻辑
aspect.After(std::forward<Args>(args)...);//核心逻辑之后的切面逻辑
}
template<typename Head, typename... Tail>
void Invoke(Args&&... args, Head&&headAspect, Tail&&... tailAspect)
{
headAspect.Before(std::forward<Args>(args)...);
Invoke(std::forward<Args>(args)..., std::forward<Tail>(tailAspect)...);
headAspect.After(std::forward<Args>(args)...);
}
private:
Func m_func; //被织入的函数
};
template<typename T> using identity_t = T;
//AOP的辅助函数简化调用
template<typename... AP, typename... Args, typename Func>
void Invoke(Func&&f, Args&&... args)
{
Aspect<Func, Args...> asp(std::forward<Func>(f));
asp.Invoke(std::forward<Args>(args)..., identity_t<AP>()...);
}
/*TEST CODE
struct TimeElapsedAspect
{
void Before(int i)
{
m_lastTime = m_t.elapsed();
}
void After(int i)
{
cout <<"time elapsed: "<< m_t.elapsed() - m_lastTime << endl;
}
private:
double m_lastTime;
Timer m_t;
};
struct LoggingAspect
{
void Before(int i)
{
std::cout <<"entering"<< std::endl;
}
void After(int i)
{
std::cout <<"leaving"<< std::endl;
}
};
void foo(int a)
{
cout <<"real HT function: "<<a<< endl;
}
int main()
{
Invoke<LoggingAspect, TimeElapsedAspect>(&foo, 1); //织入方法
cout <<"-----------------------"<< endl;
Invoke<TimeElapsedAspect, LoggingAspect>(&foo, 1);
return 0;
}
*/
```

View File

@@ -0,0 +1,140 @@
## 使用c++11解决内存泄露的问题
### 4.1 shared_ptr共享的智能指针
1. 初始化<br>
优先通过make_shared来构造智能指针
```
std::shared_ptr<int> p(new int(1));
std::shared_ptr<int> p2=p;
std::shared_ptr<int> ptr;
ptr.reset(new int(1));
```
2. 获取原始指针<br>
```
std::shared_ptr<int> ptr(new int(1));
int *p=ptr.get();
```
3. 指定删除器
```
void DeleteIntPtr(int *p){
delete p;
}
std::shared_ptr<int> p(new int,DeleteIntPtr);
//当然也可以用lambda
std::shared_ptr<int> p(new int,[](int* p){delete p;});
```
#### 4.2.1 使用shared_ptr需要注意的问题
1. 不要用一个原始指针初始化多个shared_ptr
```
int* ptr=new int;
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr);
```
2. 不要在函数实参中创建shared_ptr
```
function(shared_ptr<int>(new int),g()) //有缺陷,不同编译器的执行顺序不一样
shared_ptr<int> p(new int());
f(p,g());
```
3. 通过shared_from_this()返回的this指针不要将指针作为shared_ptr返回出来因为this本子上是个裸指针。
4. 避免循环引用
```
struct A;
struct B;
struct A{
std::shared_ptr<B> bptr;
};
struct B{
std::shared_ptr<A> bptr;
};
void TestPtr(){
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr=bp;
bp->aptr=ap;
//计数不会归零,无法正确销毁
}
}
```
### 4.2 unique_ptr独占的智能指针
它不允许其他的智能指针共享内部的指针不允许通过复制将unique_ptr赋值给另一个unique_ptr<br>
但是可以通过std::move将拥有权转移
```
std::shared_ptr<T> myPtr(new T);
std::shared_ptr<T> myOtherPtr=std::move(myPtr);
```
### 4.3 weak_ptr弱引用的智能指针
弱引用指针weak_ptr是用来监视shared_ptr的生命周期的不会使引用计数加1.同时他可以解决返回this指针和解决循环引用的问题。
#### 4.3.1 weak_ptr基本用法
1. 通过use_count()方法来获得当前观测资源的引用计数
```
std::shared_ptr<int> sp(new int(10));
std::weak_ptr<int> wp(sp);
std::cout<<wp.use_count()<<std::endl;
```
2. 通过expired()来判断资源是否释放
```
std::shared_ptr<int> sp(new int(10));
std::weak_ptr<int> wp(sp);
if(wp.expired())
{
std::cout<<"weak_ptr无效,资源释放”<<std::endl;
}else{
std::cout<<"weak_ptr有效"<<std::endl;
}
```
3. 通过lock方法获取所监视的shared_ptr
#### 4.3.2 weak_ptr返回this指针
通过派生std::enable_shared_from_this类并通过shader_from_this来返回智能指针
#### 4.3.3 weak_ptr解决循环引用问题
```
struct A;
struct B;
struct A{
std::shared_ptr<B> bptr;
};
struct B{
std::weak_ptr<A> bptr;
//改成weak_ptr就能正常销毁了
};
void TestPtr(){
{
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr=bp;
bp->aptr=ap;
}
}
```
### 4.4通过智能指针管理第三方库分配的内存
```
void* p=GetHandle()->Create();
std::shared_ptr<void> sp(p,[this](void*p){GetHandle()->Release(p);});
```
包装公共函数
```
std::shared_ptr<void> Guard(void* p)
{
return std::shared_ptr<void> sp(p,[this](void *p){GetHandle()->Release(p);});
}
```
但是不够安全
```
void *p=GetHandle()->Create();
Guard(p);
//Guard(p);是个右值如果没有被赋予给auto指针这句结束就会释放从而导致p提前释放
```
可以使用宏来解决
```
#define GUARD(p) std::shared_ptr<void> p##p(p,
void *p=GetHandle()->Create();
Guard(p);
```

View File

@@ -0,0 +1,28 @@
#### 继承构造函数
```
struct A{
void f(double i){}
A(){}
A(int i){}
A(float f){}
};
struct B:A{
using A:A;
using A:f;
void f(double i){}
}
int main(){
B b;//会调用A的对应构造函数
b.f;//会调用A的函数
}
```
不过使用了继承构造函数,就无法通过构造函数初始化派生类成员了。
#### 用户自定义字面量
可以定义出2.0f或者123_w之类的写法表达瓦特与浮点

View File

@@ -0,0 +1,96 @@
#### 2.1.2 __func__
__func__ 宏:返回函数名称 c++11兼容c99标准
#### 2.1.4 变长参数宏以及__VA_ARGS__
```
#define LOG(...){\
fprintf(stderr,"%s: Line %d:\t",__FINE__,__LINE__);\
fprintf(stderr,__VA_ARGS__);\
fprintf(stderr,"\n");\
}
int main(){
int x=3;
LOG("x=%d",x);//2-1-5.cpp: line 12: x=3
}
```
#### 2.4 宏__cplusplus
可以用于判断是否是c++编译环境同时也可以判断c++版本防止c++11程序在c++98编译器下编译
#### 2.5.1
assert宏定义在cassert头文件中
```
assert(expr);
```
首先对expr求值如果表达式为假assert输出信息并且终止程序运行。为真则什么也不做。
在开头使用需要写在cassert头文件之前因为本质上还是宏判断
```
#define NDEBUG
```
可以跳过检测。在VC++里面release会在全局定义NDEBUG。<br>
既然定义了NDEBUG还可以
```
#ifndef
//do any debug thing
//__FILE__ 文件名字面值
//__LINE__ 当前行号字面值
//__TIME__ 文件编译时间字面值
//__DATE__ 文件编译时间字面值
//__func__ 函数名称 c++11兼容c99标准
//用于打印debug信息
#endif
```
## error用法
```
#ifndef UNIX
#error This software requires the UNIX OS.
#endif
#if !defined(__cplusplus)
#error C++ compiler required.
#endif
```
#### 2.5.2 静态断言与static_assert
编译期断言
static_assert(false,"error string");
#### 2.6 noexcept修饰符和noexcept操作符
```
void excpt_func() noexcept;//虽然这样函数就不会抛出异常,但是如果抛出了异常,程序会直接被终止
void excpt_func() noexcept(expr);//如果为true则不会抛出异常
```
#### 2.9 扩展的friend语法
```
class A{
};
class B{
friend class A;//都通过
};
class C{
friend B;//c++11通过c++98失败
};
```
可以省略class直接声明类的友元这个改动的意义在于类模板,不过使用内置对象时就会实例化成一个没有友元的类
```
class A;
template <class T>
class People{
friend T;
};
```
#### 模板函数的默认模板参数
```
template<typename T=int>
void func(){
}
```
#### 2.12.1 外部模板
在头文件中定义模板类的情况下为了防止编译器给每个包含头文件的cpp文件生成代码可以添加extern关键字。当然至少要有一个是不加extern的。
```
template <typename T> void func(T){
}
extern template void func<int>(int);
```

View File

@@ -0,0 +1,11 @@
#### 5.1.3 强类型枚举
```
enum class Type {a,b,c,d};
```
优势:
1. 强作用域
2. 转换限制(禁止隐式转换)
3. 可以指定底层类型
```
enum class Type : char {a,b,c,d};//指定了类型
```