[C++]继承与面向对象设计

本文探讨了C++中几种设计模式,如Template Method和Strategy模式,用于替代virtual函数以减少调用开销。通过实例展示了如何利用这些模式为游戏角色设计灵活的健康值计算方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

C++设计模式(避免virtual函数)

本文讨论C++中一些特别的设计模式来避免使用virtual函数。(因为调用virtual函数的开销比较大。)

假设我们现在要设计一个游戏,为这个游戏内的人物设计一个继承体系。

class GameCharacter {
public:
    virtual int healthValue() const;  // 会有一个算法来计算。
    ...
};

这是一个非常显而易见的设计,但接下来,将介绍几种设计模式来完善这个类。

Template Method设计模式

这个模式主张virtual函数总是priavte。

class GameCharacter {
public:
    int healthValue() const {
        ...
        int retVal = doHealthValue();
        ...
        return retVal;
    }
    ...
private:
    virtual int doHealthValue() const {
        ...
    }
};

这个设计令客户通过public non-virtual成员函数间接调用private virtual函数,称为“non-virtual interface(NVI)手法”。他是所谓template method设计模式的一个独特表现方式。

NVI手法的一个优点是可以在虚函数调用前和调用后,附加一些相关额操作,确保虚函数进行真正工作之前和之后被调用。相当于是设定好一个场景让虚函数起作用。

并且,NVI手法允许derived class重新定义virtual函数,从而赋予他们“如何实现机能”的控制能力,但base class保留着“函数何时被调用”的权利。

Strategy设计模式-Function Pointer

此方法让函数指针成为成员,从而能够改变健康值的计算算法。

class GameCharacter; // forward declaration;
// default algorithm
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
    typedef int (*HealthCalcFunc) (const GameCharacter&);
    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {
    }
    int healthValue() const {
        return healthFunc(*this);
    }
    void setFunc(HealthCalcFunc anoFunc) {
        healthFunc = anoFunc;
    {
    ...
private:
    HealthCalcFunc healthFunc;
};

这个设计方式可以给healthValue的算法提供弹性,从而让不同的对象可以有不同的计算方法,甚至在不同的时期可以有不同的计算方法。

但这种设计模式也有可能出现问题,例如如果计算方法不能仅通过public接口提供的信息计算出来,解决这个问题的方法就是“弱化class的封装”,通过给出特定信息的访问函数,或者设定friend函数。

运用函数指针来替换virtual函数,其优点就在于,每个对象都可以有个字的计算函数,并且在运行期改变计算函数,但伴随着类的封装性降低。是否值得,得根据具体情况而定。

附言:typedef用法

在编程中使用typedef目的一般有两个,一个是给变量一个易记且意义明确的新名字,另一个是简化一些比较复杂的类型声明。

使用方法: typedef existing_type new_type_name ;

最简单使用:
typedef int size; // 把size当做int使用
typedef unsigned int WORD;
数组和指针
typedef char Line[81]; 
Line text, secondline; // 把Line定义为char[81]

typedef char* pstr;   // 把pstr定义为char*
pstr str = "abc";
int mystrcmp(pstr, pstr);
函数
    void printHello(int i);
    typedef void (*pFunc)(int); // 把pFunc定义为参数为int,返回类型为void的函数指针。
    pFunc temp;
    temp = printHello;
    temp(110);

Strategy设计模式–function对象

使用函数指针显得太过拘束。我们总是希望有更大的操作弹性,例如可以使用函数对象、成员函数指针,某些安全的隐式类型转换。所以我们优化函数指针而使用function容器。

首先我们先来介绍一下function和bind。

function

在C++中,可调用实体主要包括函数,函数指针,函数引用,可以隐式转换为函数指定的对象,或者实现了opetator()的对象(即C++98中的functor)。C++0x中,新增加了一个std::function对象,std::function对象是对C++中现有的可调用实体的一种类型安全的包裹(我们知道像函数指针这类可调用实体,是类型不安全的)。

包装普通函数
int Add(int x, int y) {
    return x + y;
}
function<int (int, int) > f = Add; // 第一个参数是返回类型,省下的都是参数类型。
int z = f(2, 3);
包装函数对象
class CxxPrint {  
public:  
    size_t operator()(const char*) { ... }  
};  
CxxPrint CPrint;
function< size_t (const char*) > f;
f = CPrint;
f(char ...);
包装类的成员函数
#include <functional>
class TAdd {
public:
    int Add(int x, int y) {
        return x + y;
    }
};

int main(void) {
    TAdd tAdd;
    function<int (TAdd*, int, int) > f = &TAdd::Add;
    cout << f(&tAdd, 2, 3);
    return 0 ;
}
保存函数对象的状态
#include <iostream>
#include <functional>
using namespace std;
class TAdd {
public:
    TAdd() : val(0) {
    }
    int operator() (int i) {
        val += i;
        return val;
    }
    int Sum() const {
        return val;
    }
private:
    int val;
};

int main(void) {
    TAdd tAdd;
    function<int (int) > f1 = tAdd;
    function<int (int) > f2 = tAdd;
    cout << f1(10) << "," << f2(10) << "," <<  tAdd.Sum() << endl;
    function<int (int) > f1_ref = ref(tAdd);
    function<int (int) > f2_ref = ref(tAdd);
    cout << f1(10) << "," << f2(10) << "," <<  tAdd.Sum() << endl;
    return 0 ;
}
/* output:
10,10,0
20,20,0
Program ended with exit code: 0
*/
function<int (int) > f1 = tAdd;

此函数对象实际上是拷贝给了f1,所以tAdd和f1是两个不同的对象。所以他们之间并不会相关关联,所以一个输出结果会是三个不同对象的输出。同时C++也提供了ref和cref函数,来提供对象的引用和常引用的包装。于是就可以正确的保存函数对象的状态了。

bind

// bind example
#include <iostream>     // std::cout
#include <functional>   // std::bind

// a function: (also works with function object: std::divides<double> my_divide;)
double my_divide (double x, double y) {return x/y;}

struct MyPair {
    double a,b;
    double multiply() {return a*b;}
};

int main () {
    using namespace std::placeholders;    // adds visibility of _1, _2, _3,...

    // binding functions:
    auto fn_five = std::bind (my_divide,10,2);               // returns 10/2
    std::cout << fn_five() << '\n';                          // 5

    auto fn_half = std::bind (my_divide,_1,2);               // returns x/2
    std::cout << fn_half(10) << '\n';                        // 5

    auto fn_invert = std::bind (my_divide,_2,_1);            // returns y/x
    std::cout << fn_invert(10,2) << '\n';                    // 0.2

    auto fn_rounding = std::bind<int> (my_divide,_1,_2);     // returns int(x/y)
    std::cout << fn_rounding(10,3) << '\n';                  // 3

    MyPair ten_two {10,2};

    // binding members:
    auto bound_member_fn = std::bind (&MyPair::multiply,_1); // returns x.multiply()
    std::cout << bound_member_fn(ten_two) << '\n';           // 20

    auto bound_member_data = std::bind (&MyPair::a,ten_two); // returns ten_two.a
    std::cout << bound_member_data() << '\n';                // 10

    return 0;
}

(From cplusplus.com)

auto是一种自动类型,会在运行的时候自动转化为相应的类型。

优化

class GameCharacter; // forward declaration;
// default algorithm
int defaultHealthCalc(const GameCharacter& gc);
class GameCharacter {
public:
    typedef std::function<int (const GameCharacter&) > HealthCalcFunc;
    explicit GameCharacter(HealthCalcFunc hcf = defaultHealthCalc) : healthFunc(hcf) {
    }
    int healthValue() const {
        return healthFunc(*this);
    }
    void setFunc(HealthCalcFunc anoFunc) {
        healthFunc = anoFunc;
    {
    ...
private:
    HealthCalcFunc healthFunc;
};

function类型产生的对象可以持有任何与此签名式兼容的可调用物。所谓兼容,意思是这个可调用物的参数可被隐式转换为const GameCharacter&,而其返回类型可以被隐式转换为int。

这个设计模式使得“指定函数算法”这件事上有惊人的弹性:

short calcHealth(const GameCharacter&);

struct HealthCalculator {
// 函数对象
    int operator()(const GameCharacter&) const {
    ...
    }
};
class GameLevel {
public:
    float health(const GameCharacter&) const;
    ...
};
class EvilBadGuy : public GameCharacter {
    ...
};

EvilBadGuy ebg1(calcHealth); // 使用函数
EvilBadGuy ebg2(HealthCalculator()); // 使用函数对象
GameLevel currentLevel;
...
EvilBadGuy edg3(
    std::bind(&GameLevel::health, currentLevel, _1));  // 使用成员函数。
    // 实际上就是调用currentLevel对象里的health函数作为edg3成员。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值