简体   繁体   English

使用基类指针派生类对象

[英]Using Base Class Pointers to Derived Class Objects

I'm currently working on the examples from "C++ through Game Programming" and I came to this example that proves polymorphism 我目前正在研究“通过游戏编程进行C ++”中的示例,并得出了证明多态的示例

#include <iostream>

using namespace std;

class Enemy
{
public:
    Enemy(int damage = 10);
    virtual ~Enemy();
    void virtual Attack() const;

protected:
    int* m_pDamage;
};

Enemy::Enemy(int damage)
{
    m_pDamage = new int(damage);
}

Enemy::~Enemy()               
{
    cout << "In Enemy destructor, deleting m_pDamage.\n";
    delete m_pDamage;
    m_pDamage = 0;
}

void Enemy::Attack() const
{
    cout << "An enemy attacks and inflicts " << *m_pDamage << " damage points.";
}  

class Boss : public Enemy
{
public:
    Boss(int multiplier = 3); 
    virtual ~Boss();
    void virtual Attack() const;

protected:
    int* m_pMultiplier; 
};

Boss::Boss(int multiplier)
{
    m_pMultiplier = new int(multiplier);
}

Boss::~Boss()                 
{
    cout << "In Boss destructor, deleting m_pMultiplier.\n";
    delete m_pMultiplier;
    m_pMultiplier = 0;
} 

void Boss::Attack() const
{
    cout << "A boss attacks and inflicts " << (*m_pDamage) * (*m_pMultiplier)
         << " damage points.";
} 

int main()
{
    cout << "Calling Attack() on Boss object through pointer to Enemy:\n";
    Enemy* pBadGuy = new Boss();
    pBadGuy->Attack();

    cout << "\n\nDeleting pointer to Enemy:\n";
    delete pBadGuy;
    pBadGuy = 0;

    return 0;
}

My question is, why this line was used: 我的问题是,为什么使用此行:

Enemy* pBadGuy = new Boss()

instead of 代替

Boss badGuy;
badGuy.Attack();

?

The author calls it "Using babes Class Pointers to Derived Class Objects". 作者称其为“将宝贝类指针用于派生类对象”。 Is it used often ? 它经常使用吗? Does it has any advantages in respect to the "normal" instantiate method ? 与“常规”实例化方法相比,它有什么优势吗?

Thanks! 谢谢!

This gives an example of how virtual methods work. 这给出了虚拟方法如何工作的示例。 Even though you're invoking a base class's method, it is the subclass's method that gets invoked. 即使您正在调用基类的方法,也将调用子类的方法。

Your proposed alternative does not clearly demonstrate this key concept. 您提议的替代方案没有清楚地说明这个关键概念。

Both alternatives accomplish the same thing, but this is meant to give an example of virtual method dispatch. 两种选择都可以完成相同的事情,但这仅是给出虚拟方法分派的示例。

Think about the following: you have a hammers and saws. 考虑以下问题:您有一把锤子和锯子。 They're both tools. 它们都是工具。 They are in bags (like vectors), and if you want to use them, you pull out one from the bag, and then you use it(call the object's method). 它们放在袋子中(如矢量),如果要使用它们,请从袋子中取出一个,然后使用它(调用对象的方法)。 You cannot put hammers in saws' bag and vice-versa, but you can put them in a bag called tools. 您不能将锤子放在锯子的袋子中,反之亦然,但是可以将锤子放在称为工具的袋子中。 It's not easier to have one bag instead of two. 拥有一个袋子而不是两个袋子并不容易。 If you want to use them, you use it in an appropiate way (call the virtual method). 如果要使用它们,请以适当的方式使用它(调用虚拟方法)。 So your enemies can be processed easier with polymorphism then without. 因此,使用多态可以轻松处理您的敌人,而无需处理。 Also, you can create methods, like 另外,您可以创建方法,例如

 // in your class...
 virtual void attackedByOther(const Enemy& other);

In this case you only need one function implemented more times, which does the damage calculation, etc. 在这种情况下,您只需要将一个函数实现多次,即可进行损害计算等。

So answering your question: if you use pointers you can do polymorphism, but with instanced you can't. 因此回答您的问题:如果使用指针,则可以进行多态操作,但是使用实例则不能。

I hope you understand! 我希望你明白!

Just to add a little to what the other people have said... 只是为了增加其他人的发言...

One of the main reasons to use abstract classes and virtuals, is so that you can have multiple object types in the same array. 使用抽象类和虚函数的主要原因之一是使您可以在同一数组中拥有多个对象类型。

Here's an example from a project I did in school: 这是我在学校所做的一个项目示例:

            char buffer = '\0';
            int itemindex = 0;
            Item* myitem;//.................................. I used a polymorphic pointer Item

            while (!myfile.get(buffer).fail()){//............ Check for failure each loop iteration 

                if (buffer == 'N' || buffer == 'P') {

                    if (_noOfItems - 1 >= itemindex) {//.... -1 because of index 0 and >= to account for the first set of entries
                        delete _items[itemindex];//.......... if it is >= than there has already been allocation at that index, so it must be freed  

                    }//...................................... to avoid memory leak
                    if (buffer == 'P') {
                        myitem = new Perishable();//......... polymorphic pointer static type is now perishable (dynamic will always be item)

                    } else if (buffer == 'N') {//............... else is extra safe
                        myitem = new NonPerishable();//....... polymorphic pointer static type is now nonPerishable (dynamic is item)  

                    }  

                    myfile.ignore();//....................... ignore comma 
                    myitem->load(myfile);

                    _items[itemindex] = myitem;//............ This line assigns myitem to the item index, since its polymorphic, only have to write
                                               //............. it once, within the 'N' || 'P' scope.
                    itemindex++;//........................... This must incriment every time  'N' || 'P' is encountered, cause each represents an
                }//.......................................... item entry.

            }

What we had to do was create Perishable and Nonperishable items. 我们要做的是创建易腐烂和不易腐烂的物品。 If you follow the comments, you will see I created an Item (base class) pointer, then based on the file that's being read, if the char is 'P' I create a Perishable object (derived) or NonPerishable object (also derived). 如果遵循这些注释,您将看到我创建了一个Item(基类)指针,然后基于正在读取的文件,如果char为'P',我将创建一个Perishable对象(派生)或NonPerishable对象(还派生) 。

So the point here, is that _items[itemindex] = myitem; 所以这里的重点是_items[itemindex] = myitem; is only called once, rather than in each condition branch buffer = P/N 仅被调用一次,而不是在每个条件下都被调用分支buffer = P/N

There's a few interesting things going on in this example, but as I mentioned, both Perishable (child) and NonPerishable (child) are in the Item (parent) array. 在此示例中,发生了一些有趣的事情,但是正如我提到的,易腐性(子级)和非易腐性(子级)都在Item(父级)数组中。

So if we were dealing with bosses (child) and characters (child) and they were both entities (parent), you could loop through your entity (parent) array containing both derived types, and call the same function... something like this. 因此,如果我们要处理老板(子)和角色(子),并且它们都是实体(父),则可以遍历包含两种派生类型的实体(父)数组,并调用相同的函数...类似这样。

for(int i = 0; i < entityList.length; i++){
    entityList[i].attack
}

Now that is /really/ cool. 现在/真的/很酷。 You can tell all of the entities to do the same thing, in one line, all because they are have the same parent type. 您可以告诉所有实体在同一行中执行相同的操作,这都是因为它们具有相同的父类型。

What you really need to know though, is what was already mentioned about dynamic dispatch. 但是,您真正需要知道的是有关动态调度的内容。 The way this was explained to me is simple: An object can have a dynamic type and a static type. 向我解释的方法很简单:对象可以具有动态类型和静态类型。

The dynamic type is the type the reference was created with, like this: 动态类型是创建引用的类型,如下所示:
Item* myitem //Dynamic type is Item

The static type is what the pointer /currently/ points to. 静态类型是指针/ currently /指向的类型。 So right now the static type of myitem is also type Item. 因此,现在myitem的静态类型也是Item类型。

If I do this: 如果我这样做:

myitem = new NonPerishable();

myitem pointer is now pointing to a child type 'NonPerishable'. myitem指针现在指向子类型“ NonPerishable”。 The dynamic type doesn't change, because it was created as an Item. 动态类型不会更改,因为它是作为项目创建的。 So dynamic type is /still/ type Item. 所以动态类型是/ still /类型Item。 The static type is however now a NonPerishable, because the Item pointer (myitem) is now pointing to a NonPerishable object. 但是,静态类型现在是Nonperishable对象,因为Item指针(myitem)现在指向了NonPerishable对象。

Note: Dynamic is what it was created as (this may be counter intuitive in the name, but it is the case) 注意:动态就是它的创建方式(名称可能很直观,但是确实如此)

Lastly, if you have a parent class and child class that both have a function with the same name but different implementation you will either get early binding or late binding (aka dynamic dispatch). 最后,如果您的父类和子类都具有相同名称的函数,但实现方式不同,则将获得早期绑定或后期绑定(也称为动态调度)。

Early binding means that the function that fires will be the parent function, dynamic dispatch means the function that fires will be the child function. 早期绑定意味着将触发的功能将是父功能,动态调度意味着将触发的功能将是子功能。 Early binding is the default for C++, to get dynamic dispatch you must declare the function as 'virtual', then the default will be the child function. 早期绑定是C ++的默认设置,要进行动态分派,必须将函数声明为“虚拟”,然后默认设置为子功能。

One way you can override binding is by explicitly declaring the namespace. 覆盖绑定的一种方法是显式声明名称空间。 Here is an example: 这是一个例子:

class Parent; //pseudo code
class Child : public Parent

object.Child::myfunction()

Note: Normally, if Parent and Child have the same function (myfunction) it will be early binding (the parent version). 注意:通常,如果“父级”和“子级”具有相同的功能(“我的功能”),它将被早期绑定(“父级”版本)。 In this case, you use the child:: namespace so it will call the child version of myfunction. 在这种情况下,您使用child ::命名空间,因此它将调用myfunction的子版本。

It's a lot of information, if you have any questions I can elaborate. 如果您有任何疑问,我可以提供很多信息。

I am just going to elaborate on what @SamVarshavchik said, and answer your questions in a sequential manner. 我将详细说明@SamVarshavchik所说的内容,并按顺序回答您的问题。

  1. This particular declaration has been used as this helps us utilize the concept of dynamic binding and use of virtual functions. 使用此特定声明是因为这有助于我们利用动态绑定和使用虚函数的概念。 The details on each of these can be found online. 可以在网上找到有关每一个的详细信息。 To tell in short, dynamic binding is the concept when the object is bound to the function at run-time and not during compile time, and virtual functions are those that support dynamic binding. 简而言之,动态绑定是在运行时而不是在编译时将对象绑定到函数的概念,而虚函数是支持动态绑定的函数。 Moreover dynamic binding can only be performed with pointers and references and not with objects themselves and since pointer to base class can be used as a pointer to derived class we use the given declaration. 此外,动态绑定只能用指针和引用执行,而不能用对象本身执行,并且由于指向基类的指针可以用作指向派生类的指针,因此我们使用给定的声明。

  2. "Is it used often?" “它经常使用吗?” Yes, it is used very often, more so whenever polymorphism and the concept of inheritance is involved as it is not possible every time to know beforehand for which class the user intended to call the respective function. 是的,它经常被使用,因此在涉及多态性和继承的概念时会更多地使用它,因为不可能每次都事先知道用户打算针对各个类调用哪个函数。

  3. I think by now I have already answered your last question as I said since dynamic binding can be performed either with pointers or reference to base class and not with object we need to use the given method. 我认为到目前为止,我已经回答了您的最后一个问题,因为动态绑定可以使用指针或对基类的引用而不是对象的引用来执行,我们需要使用给定的方法。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 使用具有指向派生类对象的指针的基类指针数组调用派生类方法 - Calling derived class methods using array of base class pointers which has pointers to derived class objects 通过指向基类对象的指针来操作派生类对象的指针 - Manipulating with pointers to derived class objects through pointers to base class objects 制作基类指针的向量并将派生类对象传递给它(多态) - Making a vector of base class pointers and pass Derived class objects to it (Polymorphism) 如何在指向基类对象的指针向量中引用派生对象? - How to reference derived objects in a vector of pointers to base class objects? 指向包含基类和派生类 class 对象的指针的向量 - 访问派生类特定变量 - Vector of pointers to base class containing base and derived class objects - accessing derived-class specific variables 使用静态分配的派生 class 对象来初始化基本 class 指针数组 - Using statically allocated derived class objects to initialize an array of base class pointers 指向包含派生类对象和重载&lt;&lt;运算符的基类的指针数组 - Array of pointers to the base class that contains objects of derived classes and overloaded << operator 模板基类可以存储指向派生对象的指针,但不能存储指向对象的指针-为什么? - Template base class can store pointers to derived but not objects - why? 使用基类指针调用带有派生类参数的模板化函数 - Call templated function with derived class arguments using base class pointers 无法使用基类指针调用派生类函数 - Unable to call derived class function using base class pointers
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM