Contents

开源软件:CGraph 语法解释

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

C++ ()和

cppreference.com : Punctuation

  • ()用来表示函数调用,包括普通函数和构建函数,以及可以给内建类型初始化

    1
    
    int *p = new int[1000](); // 内建类型圆括号初始化是语法,是标准
    
  • {} 提供列表初始化的功能,最终调用的是构造函数

    1
    2
    
    Point{} // 调用的是Point::Point()构造函数
    Point{x, y} // 嗲用的是Point::Point(x, y)构造函数
    

    构造一个匿名的类直接使用Point{}即可,它调用构造函数,但是是临时的,并没有名字指向它,调用完之后再无法找到这个匿名类。

CGraph 中使用

1
2
3
/* 定义GElementPtr类型的变量 */
GElementPtr a, b, c, d = nullptr;
status += pipeline->registerGElement<MyNode2>(&d, {b, c}, "nodeD");    // 将名为nodeD,依赖{b,c}执行的node信息,注册入pipeline中

函数原型:

1
2
3
4
5
template<typename T, CLevel level, c_enable_if_t<std::is_base_of<GElement, T>::value, int>>
CStatus GPipeline::registerGElement(GElementPtr *elementRef,
                                    const GElementPtrSet &dependElements,
                                    const std::string &name,
                                    CSize loop)

GElementPtrSet 定义:

1
using GElementPtrSet = std::set<GElementPtr>;

所以这里 pipeline->registerGElement<MyNode2>(&d, {b, c}, "nodeD"); 是使用 b, c 直接构造了 GElementPtrSet 作为函数参数。

[[nodiscard]]

C++17引入了[[nodiscard]]属性。

当用于描述函数的返回值时,如果调用函数的地方没有获取返回值时,编译器会给予警告

当用于描述类或枚举类时,如果函数的返回值是该类或枚举类的对象时(引用或指针不可以),如果该返回值没有被获取,编译器给予警告。

friend class

友元函数

在友元函数内部就可以访问该类对象的私有成员。

将全局函数声明为友元的写法如下:

1
friend  返回值类型  函数名(参数表);

将其他类的成员函数声明为友元的写法如下:

1
friend  返回值类型  其他类的类名::成员函数名(参数表);

但是,不能把其他类的私有成员函数声明为友元。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
using namespace std;
class CCar;  //提前声明CCar类,以便后面的CDriver类使用
class CDriver
{
public:
    void ModifyCar(CCar* pCar);  //改装汽车
};
class CCar
{
private:
    int price;
    friend int MostExpensiveCar(CCar cars[], int total);  //声明友元
    friend void CDriver::ModifyCar(CCar* pCar);  //声明友元
};
void CDriver::ModifyCar(CCar* pCar)
{
    pCar->price += 1000;  //汽车改装后价值增加
}
int MostExpensiveCar(CCar cars[], int total)  //求最贵气车的价格
{
    int tmpMax = -1;
    for (int i = 0; i<total; ++i)
        if (cars[i].price > tmpMax)
            tmpMax = cars[i].price;
    return tmpMax;
}
int main()
{
    return 0;
}

友元类

一个类 A 可以将另一个类 B 声明为自己的友元,类 B 的所有成员函数就都可以访问类 A 对象的私有和保护的成员,把类B叫做类A的友元。在类定义中声明友元类的写法如下:

1
friend class 类名;

来看如下例程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class CCar
{
private:
    int price;
    friend class CDriver;  //声明 CDriver 为友元类
};
class CDriver
{
public:
    CCar myCar;
    void ModifyCar()  //改装汽车
    {
        myCar.price += 1000;  //因CDriver是CCar的友元类,故此处可以访问其私有成员
    }
};
int main()
{
    return 0;
}

一般来说,类 A 将类 B 声明为友元类,则类 B 最好从逻辑上和类 A 有比较接近的关系。例如上面的例子,CDriver 代表司机,CCar 代表车,司机拥有车,所以 CDriver 类和 CCar 类从逻辑上来讲关系比较密切,把 CDriver 类声明为 CCar 类的友元比较合理。

友元关系在类之间不能传递,即类 A 是类 B 的友元,类 B 是类 C 的友元,并不能导出类 A 是类 C 的友元。“咱俩是朋友,所以你的朋友就是我的朋友”这句话在 C++ 的友元关系上 不成立。

友元类的应用场景

假设我们要设计一个模拟电视机和遥控器的程序。大家都之道,遥控机类和电视机类是不相包含的,而且,遥控器可以操作电视机,但是电视机无法操作遥控器,这就比较符合友元的特性了。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
#include <iostream>
using  namespace  std;
class  TV
{
     public :
       friend  class  Tele;
       TV():on_off(off),volume(20),channel(3),mode(tv){}
     private :   
       enum {on,off};
       enum {tv,av};
       enum {minve,maxve=100};
       enum {mincl,maxcl=60};
       bool  on_off;
       int   volume;
       int  channel;
       int  mode;
};
class  Tele
{
     public :
        void  OnOFF(TV&t){t.on_off=(t.on_off==t.on)?t.off:t.on;}
        void  SetMode(TV&t){t.mode=(t.mode==t.tv)?t.av:t.tv;}
        bool  VolumeUp(TV&t);
        bool  VolumeDown(TV&t);
        bool  ChannelUp(TV&t);
        bool  ChannelDown(TV&t);
        void  show(TV&t) const ;   
};
bool  Tele::VolumeUp(TV&t)
{
     if  (t.volume<t.maxve)
     {
         t.volume++;
         return  true ;
     }
     else
     {
         return  false ;
     }
}
bool  Tele::VolumeDown(TV&t)
{
     if  (t.volume>t.minve)
     {
         t.volume--;
         return  true ;
     }
     else
     {
         return  false ;
     }
}
bool  Tele::ChannelUp(TV&t)
{
     if  (t.channel<t.maxcl)
     {
         t.channel++;
         return  true ;
     }
     else
     {
         return  false ;
     }
}
bool  Tele::ChannelDown(TV&t)
{
     if  (t.channel>t.mincl)
     {
         t.channel--;
         return  true ;
     }
     else
     {
         return  false ;
     }
}
void  Tele::show(TV&t) const
{
     if  (t.on_off==t.on)
     {
         cout<< "电视现在" <<(t.on_off==t.on? "开启" : "关闭" )<<endl;
         cout<< "音量大小为:" <<t.volume<<endl;
         cout<< "信号接收模式为:" <<(t.mode==t.av? "AV" : "TV" )<<endl;
         cout<< "频道为:" <<t.channel<<endl;
     }
     else
     {
         cout<< "电视现在" <<(t.on_off==t.on? "开启" : "关闭" )<<endl;
     }
     
}
int  main()
{
     Tele t1;
     TV t2;
     t1.show(t2);
     t1.OnOFF(t2);
     t1.show(t2);
     cout<< "调大声音" <<endl;
     t1.VolumeUp(t2);
     cout<< "频道+1" <<endl;
     t1.ChannelUp(t2);
     cout<< "转换模式" <<endl;
     t1.SetMode(t2);
     t1.show(t2);
     return  0;
}

CGraph 中使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class GElement : public GElementObject {

....
protected:
    explicit GElement();
    ~GElement() override;
    virtual CStatus beforeRun();

		.....

protected:
    CBool done_ { false };                           // 判定被执行结束
    CBool linkable_ { false };                       // 判定是否可以连通计算

    .....
 
    friend class GNode;
    friend class GCluster;

    .....
};

GNode ,GCluster 等可以使用 GElement 中私有或者收保护的变量和方法。

std::enable_if & std::enable_if_t

C++ SFINAE简介和std::enable_if_t的简单使用

从模板特化开始

函数模版

What’s the meaning of std::enable_if_t = 0

测试代码

SFINAE

SFINAE : Substitution failure is not an error (匹配失败不是错误) ,我们使用模板函数时编译器会根据传入的参数来推导适配最合适的模板函数,在某些情况下,推导过程会发现某一个或者某几个模板函数推导起来其实是无法编译通过的,但只要有一个可以正确推导并编译出来,则那些推导得到的可能产生编译错误的模板函数就并不会引发编译错误,即匹配失败不是错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <iostream>

struct testA
{
    int data;
    testA(int val) : data(val){};
};

struct testB : testA
{
    typedef double value;
    testB(int val) : testA(val){};
};

template <typename T> typename T::value add(T t1, T t2)  // Definition #1
{
    return t1.data + t2.data;
}

int add(testA t1, testA t2)  // Definition #2
{
    return t1.data + t2.data;
}

int main()
{
    testA a(1), b(2);
    testB c(3), d(4);
    add(a, b);  // Call #2. Without error (even though there is no testA::value) thanks to SFINAE.
    add(c, d);  // Call #1
    return 0;
}

std::enable_if

std::enable_if顾名思义,满足条件时类型有效。

基本实现方式:

1
2
3
4
5
template<bool B, class T = void>
struct enable_if {};
 
template<class T>
struct enable_if<true, T> { typedef T type; };

主要在于第一个参数是否为true,当第一个模板参数为false的时候并不会定义type,只有在第一模板参数为true的时候才会定义type

1
2
3
4
typename std::enable_if<true, int>::type t; //正确,type等同于int
typename std::enable_if<true>::type; //可以通过编译,没有实际用处,推导的模板是偏特化版本,第一模板参数是true,第二模板参数是通常版本中定义的默认类型即void,但是一般也用不上它。
typename std::enable_if<false>::type; //无法通过编译,type类型没有定义
typename std::enable_if<false, int>::type t2; //无法通过编译,type类型没有定义

例子:

  • 偏特化

    std::is_trivial 判断是不是有默认构造函数,如果是使用默认构造函数,std::is_trivial<T>::value = true .

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
template <typename T>
typename std::enable_if<std::is_trivial<T>::value>::type SFINAE_test(T value)
{
    std::cout<<"T is trival"<<std::endl;
}

template <typename T>
typename std::enable_if<!std::is_trivial<T>::value>::type SFINAE_test(T value)
{
    std::cout<<"T is none trival"<<std::endl;
}
  • 校验函数模版
1
2
3
4
5
6
7
8
9
template <typename T> typename std::enable_if<std::is_integral<T>::value, bool>::type is_odd(T t)
{
    return bool(t % 2);
}

template <typename T, typename = typename std::enable_if<std::is_integral<T>::value>::type> bool is_even(T t)
{
    return !is_odd(t);
}

enable_if_t

1
2
template <bool _Test, class _Ty = void>
using enable_if_t = typename enable_if<_Test, _Ty>::type;

可以直接拿来作为类型来使用

  • 作为函数参数或返回值
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template <typename T> struct Check1
{
    // 如果T的类型是int,则定义函数 int read(void* = nullptr)
    template <typename U = T> U read(typename std::enable_if_t<std::is_same_v<U, int>> * = nullptr)
    {
        return 42;
    }

    // 如果T的类型是double,则定义函数 double read()
    template <typename U = T> typename std::enable_if_t<std::is_same_v<U, double>, U> read()
    {
        return 3.14;
    }
};
  • 作为模板参数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
template <typename T> struct Check2
{
    // 如果T的类型是int,则定义函数 int read()
    template <typename U = T, typename std::enable_if_t<std::is_same_v<U, int>, int> = 0> U read()
    {
        return 42;
    }

    // 如果T的类型是double,则定义函数 double read()
    template <typename U = T, typename std::enable_if_t<std::is_same_v<U, double>> * = nullptr> U read()
    {
        return 3.14;
    }
};
  • 类型偏特化
1
2
3
4
5
6
7
// T是其它类型
template <typename T, typename = void> struct zoo;

// 如果T是整型(我从网上扒代码时原文这里写的浮点,我觉得应该是整型吧,
// 这里的std::is_integral_v应该是C++新标准里的特性,但也类似于std::is_integral<T>::value,据说在C++17标准之后了。)
template <typename T> struct zoo<T, std::enable_if_t<std::is_integral_v<T>>>
{};

CGraph 中使用

1
2
3
// 兼容 std::enable_if_t 的语法
template<bool B, typename T = void>
using c_enable_if_t = typename std::enable_if<B, T>::type;
1
2
3
template<typename T, CUint capacity = CGRAPH_DEFAULT_RINGBUFFER_SIZE,
        c_enable_if_t<std::is_base_of<GMessageParam, T>::value, int> = 0>
class GMessage : public GMessageObject {}
1
2
 c_enable_if_t<std::is_base_of<GMessageParam, T>::value, int> = 0 
 // 如果 T 为 GMessageParam 的字类,第三个参数为 int ,否则在编译期报错