• friend友元


      在c++11之前,声明一个类和另外一个类的友元时,是需要使用class关键字的,但在c++11中是不需要的。如下例子可以说明其中的区别

    #include 
    using namespace std;
    
    class Poly;
    typedef Poly P;
    
    class LiLei {
        friend class Poly; //c++98 通过 c++11 通过
    };
    
    class Jim{
        friend Poly;  //c++98 失败 c++11 通过
    };
    
    class HanMeiMei{
        friend P; //c++98 失败 c++11 通过
    };
    
    int main()
    {
        return 0;    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    虽然在C++11中这是一个小的改进,却会带来一点应用的变化—程序员可以为类模板声明友元了,这在C++98中是无法做到的。

    class P;
    template  class People{
        friend T;
    };
    People

    PP; //类型P在这里是People类型的友元 People Pi; //对于int类型模板参数,友元声明被忽略

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

      从上述代码中我们看到,对于People这个模板类,在使用类P为模板参数时,P是People< p >的一个friend类。而在使用内置类型int作为模板参数的时候,People < int >会被实例化为一个普通的没有友元定义的类型。这样一来,我们就可以在模板实例化时才确定一个模板类是否有友元,以及谁是这个模板类的友元。这是一个非常有趣的小特性,在编写一些测试用例的时候,使用该特性是很有好处的。我们看看下面的例子,该例子源自一个实际的测试用例.

    #include 
    using namespace std;
    
    //为了方便定义,进行了危险的定义
    #ifdef UNIT_TEST
    #define priveate public
    #endif
    
    class Defender{
    public:
        void Defence(int x,int y){}
        void Tackle(int x ,int y){}
    private:
        int pos_x = 15;
        int pos_y = 0;
        int speed = 2;
        int stamina = 120;
    
    };
    
    class Attacker{
    public:
        void Move(int x,int y){}
        void SpeedUp(float ratio){}
    private:
        int pos_x = 0;
        int pos_y = -30;
        int speed = 3;
        int stamina = 100;
    
    };
    
    #ifdef UNIT_TEST
    class Validator{
    public:
        void Validate(int x,int y, Defender &d){}
        void Validate(int x,int y, Attacker &d){}
    };
    
    int main(){
        Defender d;
        Attacker a;
        a.Move(15,30);
        d.Defence(15,30);
        a.SpeedUp(1.5f);
        d.Defence(15,30);
        Validator v;
        v.Validate(7,0,d);
        v.Validate(1,-10,a);
        return 0;
    }
    //g++ 2-9-3.cpp -std=c++11 -DUNIT_TEST
    #endif
    
    
    • 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

      这个例子中,测试人员的目的是在一系列函数调用后,检查Attacker类变量a和Defender类变量d中成员变量的值是否符合预期。这里,按照封装的思想,所有成员变量被声明为private的。但Attacker和Defender的开发者为了方便,并没有为每个成员写Get函数,也没有为Attacker和Defender增加友元定义。
      而测试人员为了能够快速写出测试程序,采用了比较危险的做法,即使用宏将private关键字统一替换为public关键字。这样一来,类中的private成员就都成了public的。这样的做法存在4个缺点:一是如果侥幸程序中没有变量包含private字符串,该方法可以正常工作,但反之,则有可能导致严重的编译时错误;二是这种做法会降低代码可读性,因为改变了一个常见关键字的意义,没有注意到这个宏的程序员可能会非常迷惑程序的行为;三是如果在类的成员定义时不指定关键字(如public、private、protect等),而使用默认的private 成员访问限制,那么该方法也不能达到目的;四则很简单,这样的做法看起来也并不漂亮。

    #include 
    using namespace std;
    
    
    template 
    class DefenderT{
    public:
        friend T;
        void Defence(int x,int y){}
        void Tackle(int x ,int y){}
    private:
        int pos_x = 15;
        int pos_y = 0;
        int speed = 2;
        int stamina = 120;
    
    };
    
    template 
    class AttackerT{
    public:
        friend T;
        void Move(int x,int y){}
        void SpeedUp(float ratio){}
    private:
        int pos_x = 0;
        int pos_y = -30;
        int speed = 3;
        int stamina = 100;
    
    };
    
    using Defender  = DefenderT; //普通的类定义, 使用 int 做参数
    using Attacker  = AttackerT;
    
    #ifdef UNIT_TEST
    
    class Validator;
    
    using DefenderTest  = DefenderT; //测试专用的定义,Validator成为友元
    using AttackerTest  = AttackerT;
    
    class Validator{
    public:
        void Validate(int x,int y, DefenderTest &d){}
        void Validate(int x,int y, AttackerTest &a){}
    };
    
    int main(){
        DefenderTest d;
        AttackerTest a;
        a.Move(15,30);
        d.Defence(15,30);
        a.SpeedUp(1.5f);
        d.Defence(15,30);
        Validator v;
        v.Validate(7,0,d);
        v.Validate(1,-10,a);
        return 0;
    }
    //g++ 2-9-4.cpp -std=c++11 -DUNIT_TEST
    #endif
    
    
    • 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

      我们把原有的Defender和Attacker类定义为模板类DefenderT和AttackerT。而在需要进行测试的时候,我们使用Validator为模板参数,实例化出DefenderTest及AttackerTest版本的类,这个版本的特点是,Validator是它们的友元,可以任意访问任何成员函数。而另外一个版本则是使用int类型进行实例化的Defender和Attacker,按照C++11的定义,它们不会有友元。因此这个版本保持了良好的封装性,可以用于提供接口用于常规使用。值得注意的是,在代码清单2-22中,我们使用了using来定义类型的别名,这跟使用typedef的定义类型的别名是完全一样的。使用using定义类型别名是C++11中的一个新特性。

  • 相关阅读:
    单关系查询到自然链接,再到joinon
    架构师考试周报三
    美创科技入选第二届安徽省网络和数据安全应急技术支撑单位
    MySQL数据存在更新不存在新增数据
    拼多多电商官方平台根据ID获取APP商品详情原数据、券后价、商品信息、sku信息指南
    Apache Tomcat CVE-2020-1938 漏洞
    Java面试八股之myBatis动态SQL的作用
    2022-2028全球有机硅弹性密封胶行业调研及趋势分析报告
    Spring——bean的生命周期
    线程池的使用(结合Future/Callable使用)
  • 原文地址:https://blog.csdn.net/yaoyaohyl/article/details/127424412