打印

【连载】 C++14学习笔记 —— C++编程新时代.

[复制链接]
楼主: dong_abc
手机看帖
扫描二维码
随时随地手机跟帖
41
很不错

使用特权

评论回复
42
dong_abc|  楼主 | 2013-8-25 21:40 | 只看该作者
本帖最后由 dong_abc 于 2013-8-25 21:55 编辑

5、静态多态 与 CRTP
多态是一项共通的标准编程工具,派生类对象可以被当作基类的对象之实体使用,但能够调用派生对象的函数,或称方法(methods),例如以下的代码:
class Base{    
public:   
        virtual void method() { std::cout << "Base"; }
};

class Derived : public Base{   
public:   
        virtual void method() {std::cout << "Derived"; }
};

int main()
{   
     Base *pBase = new Derived;   
     pBase->method(); //outputs "Derived"   
     delete pBase;   
     return 0;
}
唤起的 virtual 函数是属于位于继承最下位之类型的。这种动态多态(dynamically polymorphic)行为是借由拥有虚函数的类型所产生的虚函数表(virtual look-up tables)来实行的。虚函数表会在运行期被查找,以决定该唤起哪个函数。因此动态多态无可避免地必须承担这些运行期成本。
然而,在许多情况下我们需要的仅是可以在编译期决定,无需变动的多态行为。那么一来,奇怪的递归模板样式(Curiously Recurring Template Pattern ;CRTP)便可被用来达成静态多态。如下例:
template <class Derived>
struct base
{   
     void interface()   
     {         
        // ...         
        static_cast<Derived*>(this)->implementation();         
        // ...   
    }
};

struct derived : base<derived>
{     
     void implementation();
};

这里基类模板有着这样的优点:成员函数的本体在被他们的声明之前都不会被实体化,而且它可利用 static_cast 并通过自己的函数来使用派生类的成员,所以能够在编译时产生出带有多态特性的对象复合物。在现实使用上,Boost迭代器库[1]便有采用 CRTP 的技法。
其他类似的使用还有"Barton-Nackman trick英文)",有时后被称作"有限制的模板扩张",共同的功能被可以放在一个基类当中,作为必要的构件使用,以此确保在缩减多余代码时的一致行为。


模板元编程的优缺点

  • 编译期对运行期:因为模板的运算以及展开都是在编译期,这会花相对较长的编译时间,但能够获得更有效率的运行码。这项编译期花费一般都很小,但对于大项目或是普遍依赖模板的程序,也许会造成很大的编译开销。
  • 泛型程序设计::模板元编程允许程序员专注在架构上并委托编译器产生任何客户码要求的实现。因此,模板元编程可达成真正的泛用代码,促使代码缩小并较好维护。
  • 可读性::对于C++来说,模板元编程的语法及语言特性比起传统的C++编程,较难以令人理解。因此对于那些在模板元编程经验不丰的程序员来说,程序可能会变的难以维护。(这要视各语言对于模板元编程语法的实现)
  • 移植性::对于C++来说,由于各编译器的差异,大量依赖模板元编程(特别是最新形式的)的代码可能会有移植性的问题。

本节内容选自维基百科http://zh.wikipedia.org/wiki/%E6%A8%A1%E6%9D%BF%E5%85%83%E7%B7%A8%E7%A8%8B
维基百科将静态多态和CRTP解释得非常清楚。

使用特权

评论回复
43
Ryanhsiung| | 2013-8-25 23:37 | 只看该作者
支持一下

使用特权

评论回复
44
dong_abc|  楼主 | 2013-8-26 00:34 | 只看该作者
Ryanhsiung 发表于 2013-8-25 23:37
支持一下

难得呀! :handshake

使用特权

评论回复
45
dong_abc|  楼主 | 2013-8-27 22:32 | 只看该作者
本帖最后由 dong_abc 于 2013-8-28 23:08 编辑

6、基于C++简易CRTP框架的嵌入式软件设计
       1)首先,还是复习一下C++的CRTP编程模式的使用。
template <class T>
struct base {   
   void interface() {         
   static_cast<T*>(this)->implementation();   
}
    void implementation();
    ...
};
struct derived : base<T> {     
    void implementation();
     ...
};
定义了一个基类模板 和 一个基类模板的派生类。
这两个类中都有 void implementation()
函数,而且在基类的void interface()接口函数中用一个静态指针指向了implementation()函数。

实例化一个基类,并将派生类作为基类的参数。
base<derived> base_t;
base_t.implementation();
调用基类的implementation()成员函数,这时是调用的基类中的implementation()函数
还是派生类中的implementation()函数呢?

由于我们已经在derived派生类中定义了implementation
()函数,并且base类实例化时的参数指向了derived派生类。
所以base_t.implementation()调用的是派生类derived中的成员函数implementation
()。

如果派生类derived中没有定义implementation
()成员函数,
那么base_t.implementation()调用的是基类base中的成员函数implementation()。

因此,base类模板既可以调用当前的成员函数,还可以调用未来(派生类)的成员函数。
此特性称为“静态多态”,CRTP即使实现一种静态的多态效应。

       2)上面已经描述了CRTP的基本使用。现在我们来做一个基于单片机的小项目,这里使用的新唐的NU_Tiny-EVB_120和 keil开发环境。本项目是一个CO探测器,其工作流程非常简单。
探测器自检初始化 ---> 传感器检测当前环境CO含量并发出状态警告 ---> 进入低功耗模式 ---> 30秒后唤醒单片机,传感器再次检测当前环境CO含量并发出状态警告 ---> 进入低功耗模式 ...以此循环。
先给出基于CRTP的基本框架。
//detector.h
#ifndef __Detector_H__
#define __Detector_H__
#include "routine.h"
template<typename T>
struct detector {   
     void fun() {
     static_cast<T*>(this)->detector_init(0,"__DATE__");
     for(;;){
        static_cast<T*>(this)->detector_poll();
        static_cast<T*>(this)->power_down();
     }
}
    void detector_poll() {
        while( static_cast<T*>(this)->power_scan() );
        static_cast<T*>(this)->sensor_scan();
        static_cast<T*>(this)->danger_warn();
        static_cast<T*>(this)->dev_state();
}
   
    void detector_init(int, char*);
    int power_scan();
    void power_down();
    void danger_warn();
    void dev_state();
    void sensor_scan();   
};
template<typename T>
void detector<T>::detector_init(int, char*) { }
template<typename T>
void detector<T>::power_down() { }
template<typename T>
int detector<T>::power_scan() {return 0;}
/*xx厂家传感器*/
template<typename T>
void detector<T>::sensor_scan()
{
routine_tt.io();//传感器扫描用翻转io来指示
}
template<typename T>
void detector<T>::danger_warn() {  }
template<typename T>
void detector<T>::dev_state() {}
/*----------------------------------------------------------------------------*/
/*yy厂家传感器*/
struct detector_a:detector<detector_a> {
/*void sensor_scan()
{
routine_tt.io();//传感器扫描用翻转io来指示
}*/
};
#endif

//main.cpp
int main (void)
{
   __enable_irq();   
   detector<detector_a> co_star;
   co_star.fun();   
   while(1)
   {
   }
}
为了方便阅读,我删除了函数中具体的内容,只留下了一个最小框架。
detector<detector_a> co_star; 
co_star.fun();
主函数中实例化了一个探测器co_star模板类,并且参数指向了detector模板的派生类detector_a,
由于我们使用的传感器类型较多,每个厂家,每个型号 的传感器的工作方式又可能不尽相同。
所以我在detector模板中预留了一种传感器(xx厂家的传感器),其扫描方式为detector<T>::sensor_scan()。

其后因市场原因,又需要使用yy厂家的传感器,所以我在detector模板的基础上派生了一个detector_a类,
同样定义yy的扫描方式为sensor_scan(),只是在detector实例化的时候,将参数指向了detector_a .
/*yy厂家传感器*/
struct detector_a:detector<detector_a>{
void sensor_scan()
{
routine_tt.io();//传感器扫描用翻转io来指示  
}

所以主函数中co_star.fun()调用的detector<detector_a>::sensor_scan()其实是指向了yy厂家传感器的扫描方式
detector_a::sensor_scan()。

这里不仅仅传感器扫描可以通过CRTP来拓展,从fun() 到 detector_poll() 到 sensor_scan() 全都可以通过CRTP的静态多态效应来拓展。
struct detector_a:detector<detector_a>{
void fun()
{
...  
}
struct detector_a:detector<detector_a>{
void detector_poll()
{
...  
}
从整个框架到一个内部函数都可用这种机制来拓展/重构,其可拓展的粒度可以是一个最小的函数调用。现在你可否体会到C++ CRTP模式的程序拓展呢?
本帖测试源代码也上传至此,具体的函数实现被我删掉了,仅以一个io翻转来做演示,请见谅。
NUC120CppDemo.rar (231.12 KB)

如果您还想深入了解CRTP框架的嵌入式软件设计,可以看看john_lee老师的课程记录
https://bbs.21ic.com/icview-577248-1-1.html

使用特权

评论回复
46
dong_abc|  楼主 | 2013-8-30 21:40 | 只看该作者
本帖最后由 dong_abc 于 2013-8-31 12:23 编辑

第五章 提高类型安全

一、强类型枚举。
由于枚举的全局特性,显得与名字空间、类必须通过“名字::成员名”的方式来访问有些格格不入,而且还破坏了名字空间、类的封装特性。
所以C++11提出了“强类型枚举”。声明强类型枚举非常简单,只需要在enum后面加上class即可。
enum class  color {whilte,black,red,yellow};
此时访问强类型枚举就需要“名字::成员名”的方式来访问了。
color a = color::red;
强类型枚举还可以显式的指定底层类型,
enum class  color:unsigned char {whilte,black,red,yellow};

二、智能指针 与 垃圾回收。
C++11增加了三个智能指针模板。unique_ptr , shared_ptr , weak_ptr .
unique_ptr留在后面再说。 shared_ptr 通过引用计数的方式来释放堆内存空间,而且允许多个该智能指针共享同一内存空间。
weak_ptr 可以指向shared_ptr 的内存空间,却并不拥有其内存空间, weak_ptr的成员lock可以返回其指向内存的一个shared_ptr对象,
且所指对象无效时返回空指针nullptr. 这在验证shared_ptr智能指针的有效性上很有用
看下面例程如何应用shared_ptr 的引用计数方式回收堆内存。
#include<memory>
#include<iostream>
using namespace std;

void check(weak_ptr<int> & wp)
{
  shared_ptr<int> sp = wp.lock();
  if(sp != nullptr)
      cout<<"still "<< *sp<<endl;
  else
      cout<<"pointer is invalid"<<endl;
}

int main()
{
  shared_ptr<int> sp1(new int(22));
  shared_ptr<int> sp2 = sp1;
  weak_ptr<int> wp = sp1;
  cout<<*sp1<<endl;      //22
  cout<<*sp2<<endl;      //22
  check(wp);

  sp1.reset();
  cout<<*sp2<<endl;      //22
  check(wp);

sp2.reset();
check(wp);                  //pointer is invalid
}
sp1.reset()并不能清除sp2的引用计数,sp2.reset()后sp2引用计数清零,检测wp得到pointer is invalid.

使用特权

评论回复
47
dong_abc|  楼主 | 2013-8-31 12:50 | 只看该作者
本帖最后由 dong_abc 于 2013-9-5 22:29 编辑

第7章  为改变思考方式而改变
1、lambda与仿函数
最简单lambda函数
[]{};
这是个函数吗? yes!
int main()
{
   int a=1;
   int b=2;
   auto sum = [=]{return a+b;};
   a++; b--;
   return sum(a+b);
}
auto sum = [=]{return a+b;}; 是求两个数的和,可以在main函数中直接调用。
lambda函数即局部函数,可以就地调用。

[a]表示以值得方式捕捉变量a ;  
auto sum = [a](int b;){return a+b;};
[=]表示以值得方式捕捉所有父作用域的变量(包括this);
auto sum = [=]{return a+b;};
[&i]表示以引用传递的方式捕捉变量i ;
auto sum = [=,&b]{return a+b;};
[&]表示以引用传递的方式捕捉所有父作用域的变量
auto sum = [&]{return a+b;};
[this]表示以值得方式捕捉this指针
//还没想好lambda函数如何利用this指针。

通过组合,lambda函数还能表达更复杂的变量捕捉。
[=,a,&b]表示以值得方式捕捉变量a,以引用的方式捕捉变量b,以值得方式捕捉其他所有父作用域的变量。
auto sum = [=,a,&b](int c){c = a+b;};

lambda函数可以不用声明,在其父作用域内就地调用,通过lambda函数,程序员可以轻松地在函数内部重用代码,而不用费心设计接口。
那个传说中的“为任何可重用的代码创建函数”的作风,因lambda局部函数的出现而更理性的思考。

未完,待续...

使用特权

评论回复
48
dong_abc|  楼主 | 2013-9-5 20:49 | 只看该作者
本帖最后由 dong_abc 于 2013-9-11 21:55 编辑

lambda函数与仿函数是非常有用的,在C++11之前,C#、PHP等高级语言已经支持lambda.
lambda还有不少亮点,这些笔记会随着我对C++的认知水平逐渐加深......

使用特权

评论回复
49
dong_abc|  楼主 | 2013-9-14 23:31 | 只看该作者
本帖最后由 dong_abc 于 2013-10-1 18:37 编辑

2、Lambda表达式的嵌套

#include <iostream>
int main()
{
   using namespace std;

   int m = [](int x)
      { return [](int y) { return y * 2; }(x) + 3; }(5);

   cout << m << endl;   //13
}

3、Lambda函数和STL
lambda函数的引入为STL的使用提供了极大的方便。比如下面这个例子,当你想遍历一个vector的时候,原来你得这么写:
vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for ( auto itr = v.begin(), end = v.end(); itr != end; itr++ )
{
    cout << *itr;
}

现在有了lambda函数你就可以这么写
vector<int> v;
v.push_back( 1 );
v.push_back( 2 );
//...
for_each( v.begin(), v.end(), [] (int val)
{
    cout << val;
} );

而且这么写了之后执行效率反而提高了。因为编译器有可能使用”循环展开“来加速执行过程(计算机系统结构课程中学的)。
这个实例选自网上某课件,用到了for_each遍历操作,for_each使用了迭代器的概念,其迭代器就是指针,而且内含了自增操作,所以++p不用写在for_each循环中。

使用特权

评论回复
50
dong_abc|  楼主 | 2013-9-14 23:43 | 只看该作者
本帖最后由 dong_abc 于 2013-9-15 00:30 编辑

4、lambda函数还有个要注意的地方忘了说,在使用lambda函数时,如果需要捕捉的值成为lambda函数的常量,则应该使用按值传递的方式捕捉;如果需要捕捉的值成为lambda函数运行时的变量,则应该采用引用传递的方式捕捉。
int main()
{
   int a=1;
   int b=1;
   auto sum = [=]{return a+1;};
   auto sum1 = [&]{return b+1;};
    return sum();  
    return sum1();
}
经过函数调用后,a的值依然是1,而b的值是2.  此时a作为sum函数的常量,b作为sum1函数的变量。
所以要注意区分值传递 与 引用传递的区别。

使用特权

评论回复
51
dong_abc|  楼主 | 2013-9-14 23:51 | 只看该作者
这是《深入理解C++11》的倒数第二章,其实中间放过了很多重要的内容,比如右值引用、原子操作与多进程、变长模板、常量元编程等等。
因为这些我也好好看了一下,不过意识较模糊,所以这一阶段都放过了,这些重要的内容必须要熟练掌握。

C++不是一种语言,而是一个语言联盟。 针对不同的方向都有不同的应用。

使用特权

评论回复
52
dong_abc|  楼主 | 2013-9-21 16:48 | 只看该作者
本帖最后由 dong_abc 于 2013-9-21 16:55 编辑

这周犯懒了,越来越懒了,什么也不想干!
上班也没弄什么新东西,基本上天天打酱油,干一些无聊的事情,长时间这样下去,直接就废掉了!
现在的大好时光,要有点目标,come on !
下周找个与工作相关的项目练练手。 编程多用C++,特别是C++11的新特性。

使用特权

评论回复
53
outstanding| | 2013-9-24 09:23 | 只看该作者
学习一下

使用特权

评论回复
54
dong_abc|  楼主 | 2013-9-28 20:15 | 只看该作者
一同学想在家里搞些智能家居的玩意,纯DIY,帮他做个简易摄像头,外面买其实更便宜,不过没办法揉进自己的系统,所以瞎折腾一下,从0开始做一个,毕竟也不复杂,不会太费事。今天休息在垃圾堆里翻出几个CMOS/CCD,一些板子,新塘的、ST的等等。

还没玩过STM32,所以就用STM32+ CMOS/CCD + LCD 来弄。 在网上找了一下资料,都有现成的,所以就拿别人的来改造,顺便练习一下C++.

使用特权

评论回复
55
dong_abc|  楼主 | 2013-9-28 20:33 | 只看该作者
:time:简易摄像头项目:STM32 + CMOS/CCD + TFT + 其他(还不知道需要其他神马)

1、原理图+TFT规格书
Schematic_arc_pangu.pdf (39.43 KB)

2、调了一天TFT,虽然调通了,不过代码太乱,还得整理。先上个systick模块,配置时序,TFT需要的。
//file: systick.h

#ifndef __SysTick_H__
#define __SysTick_H__
#ifdef __cplusplus
extern "C" {
#endif
namespace systick {
class systick_t {
public:
systick_t();
void SysTick_Delay(volatile unsigned int nTime);
void Set_TimingDelay(volatile unsigned int timing_Delay);
unsigned int Get_TimingDelay();
  
public:
volatile unsigned int TimingDelay;
private:
};
}
extern systick::systick_t systick_tt;
#ifdef __cplusplus
}
#endif
#endif

//file: systick.cpp

#include "systick.h"
#include "stm32f10x.h"
//systick名字空间定义
systick::systick_t systick_tt;   
//systick模块成员函数
systick::systick_t::systick_t()   
{
    if (SysTick_Config(SystemCoreClock / 1000))
    {
        while (1);
    }
}
void systick::systick_t::SysTick_Delay(volatile unsigned int nTime)
{
    Set_TimingDelay( nTime );
    while(Get_TimingDelay() != 0);
}
void systick::systick_t::Set_TimingDelay(volatile unsigned int timing_Delay)
{
    TimingDelay = timing_Delay;
}
uint32_t systick::systick_t::Get_TimingDelay()
{
    return TimingDelay;
}
//中断函数,外部函数 .
extern "C" void __irq SysTick_Handler(void)
{
    if (systick_tt.Get_TimingDelay() != 0x00)
    {
        systick_tt.TimingDelay--;
    }
}

使用特权

评论回复
56
dong_abc|  楼主 | 2013-9-29 00:30 | 只看该作者
我晕死,楼上项目有人做了,还开源了,做得相当好。
直接买一套回来玩算了。




使用特权

评论回复
57
dong_abc|  楼主 | 2013-9-29 19:28 | 只看该作者
本帖最后由 dong_abc 于 2014-9-14 14:05 编辑

楼上项目开源地址:http://www.amobbs.com/forum.php?mod=viewthread&tid=4585793
@sedatefire  

使用特权

评论回复
58
dong_abc|  楼主 | 2013-10-6 10:33 | 只看该作者
本帖最后由 dong_abc 于 2013-10-6 10:53 编辑

第十章 查缺补漏
C++内容繁多,各种特定环境都有特定的应用,本章对前面内容作一些查缺补漏。

1、模板类获取其他类成员访问权限的几种方式
a) 让某个类成为另一个模板类所有实例的友类
template<class T>
class A{
friend class B;
...
}

b) 让模板类继承它需要访问的类
template<class T>
class A:B{
...
}

c) 利用类作为模板的类型参数
template<class T>
class A{
...
}

template<class T>
class B{
...
}

A<B<int> > a; //两个>>之间记得加个空格,否则编译器可以认为是向右移位操作。

使用特权

评论回复
59
dong_abc|  楼主 | 2013-10-26 17:19 | 只看该作者
在DSP上奔C++看看。

使用特权

评论回复
60
dragon20100708| | 2013-10-29 11:25 | 只看该作者
Mark

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则