Search code examples
c++radixdynamic-castderived

C++ dynamic cast failing / child object gets treated as parent


All I want is the C++ equivalent of the java code for: if(ParentClassObject.myIdentifier == "Child1") (Child1) ParentClassObject ... do things

So all I want is to --Take an object I created as a Child object --Use it in a method that wasn't sure what type of Child it was, so it's treating it as a Parent Object --Cast it as the appropriate Child object

After 3 hours I haven't been able to get this work and my computer is about to become airborne. I've also read a dozen SE articles and tutorials on OOD but didn't find/didn't recognize the solution.

In the stripped down code below, CombatLogLine is the Parent and "CombatLogVersionLine" is the Child. determineTypeOfEvent is the method that created it as a child and then returned it as a parent. Full code is at https://docs.google.com/document/d/1OXF-YyR0DF0VfdUe9r4OUgPIYNRYXsiMMmQ_fkV-QIQ/edit?usp=sharing

The code went into two blocks for some reason, you might have to scroll down to see all the code in the first block. And the lines of interest have the XXXXXXXXXXs in them.

//CombatLogLine.h
#include stdafx.h
class CombatLogLine
{
    public:
    bool virtual areFieldsOk();
    void virtual parseLine();
    string typeOfEvent;
    //a bunch of stuff I removed

protected:
    string theLineOfRawText;
};

class CombatLogVersionLine : public CombatLogLine 
{
public:
    CombatLogVersionLine(string _theLineOfRawText) : CombatLogLine(_theLineOfRawText)
    {
    typeOfEvent = "COMBAT_LOG_VERSION";
}
bool areFieldsOk();
void parseLine();
private:
};

//CombatLogLine.cpp
#include "stdafx.h"
#include "CombatLogLine.h"

CombatLogLine::CombatLogLine(string _theLineOfRawText)
{
    //stuff I removed for this example involving _theLineOfRawText
    typeOfEvent = "TBA";
}

bool CombatLogLine::areFieldsOk()
{
    cout << "This is the base classes areFieldsOk" << endl;
    return false;
}


bool CombatLogVersionLine::areFieldsOk()
{
    if(typeOfEvent == "COMBAT_LOG_VERSION")
    {
        if(sizeOfLine == 2)
        {
            cout << "Hallelujah!" << endl;
            return true;
        }
    }
    return false;
}

void CombatLogVersionLine::parseLine()
{
}


// main method

....

    for(unsigned int i = 0; i < combatLogSplitIntoLines.size(); i++)
    {   
        CombatLogLine currentLine = determineTypeOfEvent(combatLogSplitIntoLines[i]);//determine type of event also creates an appropriate CombatLogLine 
        if(currentLine.typeOfEvent == "COMBAT_LOG_VERSION")
        {
            currentLine.areFieldsOk();
            cout << "type id " << typeid(currentLine).name() << endl; 
            CombatLogVersionLine * temp5 = dynamic_cast<CombatLogVersionLine *>(&currentLine);   //XXXXXXXXXXXXXXXXX I don't even want to use pointers here but think I have to
            if(temp5 == 0)
            {
            //XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX dynamic     cast fails
                cout << "The dynamic cast failed." << endl;
            system("PAUSE");
                return 0;
            }
            temp5->areFieldsOk();
            continue;
        }

...

//method in main file 
CombatLogLine determineTypeOfEvent(string _aLine)
{
    if(_aLine == "")
    {
        cout << "Something went wrong asdghaer" << endl;
        system("PAUSE");
    }
    if(_aLine.find("COMBAT_LOG_VERSION") != string::npos)
    {
        CombatLogVersionLine returnValue(_aLine);
        //XXXXXXXXXXXXXXXXXXXXXXXXXXXXX this works properly and creates a "CombatLogVersionLine"
        cout << "A CombatLogVersionLine was created. " <<     typeid(CombatLogVersionLine).name() << endl;
        return returnValue;
    }
    return (CombatLogLine("FAIL"));
}

Solution

  • C++ is not Java. C++ objects are fundamentally different than Java objects. Trying to use C++ objects like Java objects is a guaranteed path towards failure.

    Your determineTypeOfEvent() function returns a superclass:

    CombatLogLine determineTypeOfEvent(string _aLine)
    

    And it is constructing a subclass, then attempts to return it:

        CombatLogVersionLine returnValue(_aLine);
    
        // ...
    
        return returnValue;
    

    Full stop right here. Do not pass "Go". Do not collect $200. C++ doesn't work this way. This is how you do things in Java. But this is not how you do things in C++. In C++, this results in object slicing. The function will not return a CombatLogVersion subclass. The subclass gets immediately destroyed, after a CombatLogLine temporary gets constructed via its copy-constructor, returning exactly what this function is declared as returning: CombatLogLine. The subclass gets sliced away.

    Before you proceed with your project, you need to spend additional time studying how C++ objects and classes work. There are several possible ways to do this correctly. Typically, in situations like these, the function in question would be returning a std::shared_ptr<CombatLogLine>, and constructing the return value in dynamic scope; and all callers of this function using the return value accordingly.

    This would be one way to do something like this, but not the only one. Another possible alternative that might be used in similar situation would be templates, or perhaps passing a type-erased std::function callback. Hard to say, the correct approach here depends on the particulars of this application.

    The general answer here is going to be: "C++ objects don't work this way, and you should spend more time learning how C++ classes work", as well as the rest of the C++ language, its libraries and all of its features like templates, smart pointers, and function objects, in order to determine the best solution for your problem. There is no instant gratification in C++.

    You really need to completely forget how Java objects work. It only makes things more confusing if you try to use C++ objects, thinking that they're somehow analogous to Java objects. They're not. The syntax, the keywords, and the grammar looks deceptively similar, but they are completely, and fundamentally different. They work in completely different ways.