views:

84

answers:

5

I'm having a bit of trouble with dynamic_casting. I need to determine at runtime the type of an object. Here is a demo:

#include <iostream>
#include <string>

class PersonClass
{
  public:
  std::string Name;
  virtual void test(){}; //it is annoying that this has to be here...
};

class LawyerClass : public PersonClass
{
  public:
  void GoToCourt(){};
};

class DoctorClass : public PersonClass
{
  public:
  void GoToSurgery(){};
};

int main(int argc, char *argv[])
{

  PersonClass* person = new PersonClass;
  if(true)
  {
    person = dynamic_cast<LawyerClass*>(person);
  }
  else
  {
    person = dynamic_cast<DoctorClass*>(person);
  }

  person->GoToCourt();


  return 0;
}

I would like to do the above. The only legal way I found to do it is to define all of the objects before hand:

  PersonClass* person = new PersonClass;
  LawyerClass* lawyer;
  DoctorClass* doctor;

  if(true)
  {
    lawyer = dynamic_cast<LawyerClass*>(person);
  }
  else
  {
    doctor = dynamic_cast<DoctorClass*>(person);
  }

  if(true)
  {
    lawyer->GoToCourt();
  }

The main problem with this (besides having to define a bunch of objects that won't be use) is that I have to change the name of the 'person' variable. Is there a better way?

(I am not allowed to change any of the classes (Person, Lawyer, or Doctor) because they are part of a library that people who will use my code have and won't want to change).

Thanks,

Dave

A: 

dynamic_cast allows you to obtain a more precisely-typed reference or pointer to an object of a given type.

It does not allow you to change the type of the object. The type of a constructed object cannot change in C++. You must construct a new object.

LawyerClass lawyer( person );

EDIT: to adapt your sample into a rough demo of polymorphism,

  PersonClass* person = NULL;
  if(true)
  {
    person = new LawyerClass;
  }
  else
  {
    person = new DoctorClass;
  }

  if ( LawyerClass *lawyer = dynamic_cast< LawyerClass * >( person ) )
  {
    lawyer->GoToCourt();
  }

Also, you should use an "empty" virtual destructor rather than virtual void test() {}.

Potatoswatter
That's what it was looking like... but how can I create this new LawerClass object in a way that it is accessible outside of the conditional? (without defining all of the possible objects I COULD create before the conditional, because that seems insane!)
David Doria
@David: use polymorphism to handle the object through a base class pointer. See my edit.
Potatoswatter
A: 

I may be completely missing the point here or I may be misunderstanding you example so if I am let me know and I will delete my post.

But would it not make more sense to have a public method called doJob (or something similar) that calls the virtual method. That way you could do this:

#include <iostream> 
#include <string> 
using namespace std;

class PersonClass 
{ 
public: 
    std::string Name; 
    virtual void doWork(){}; //it is annoying that this has to be here... 
}; 

class LawyerClass : public PersonClass 
{ 
public: 
    void doWork(){GoToCourt();}
    void GoToCourt(){cout<<"Going to court..."<<endl;} 
}; 

class DoctorClass : public PersonClass 
{ 
public: 
    void doWork(){GoToSurgery();}
    void GoToSurgery(){cout<<"Doing surgery..."<<endl;}; 
}; 

int main(int argc, char *argv[]) 
{ 

    PersonClass* person; 
    if(true) 
    { 
        person = new LawyerClass(); 
    } 
    else 
    { 
        person = new DoctorClass(); 
    } 

    person->doWork(); 


    return 0; 
} 
David Relihan
David,Unfortunately I cannot change the classes as they are part of a library :( Of course your solution is the most reasonable if I could!
David Doria
Damn - I knew there had to be a catch! Mmmm interesting problem...
David Relihan
+1  A: 

Dynamic casting to a subclass and then assigning the result to a pointer to superclass is of no use - you practically are back where you started. You do need a pointer to a subclass to store the result of the dynamic cast. Also, if the concrete type of your object is PersonClass, you can't downcast it to a subclass. Dynamic casting can only work for you if you have a pointer to a superclass but you know that the object pointed to is actually an instance of a subclass.

As others have pointed out too, the best option would be to redesign the class hierarchy to make your methods really polymorphic, thus eliminate the need for downcasting. Since you can't touch those classes, you need the downcast. The typical way to use this would be something like

PersonClass* person = // get a Person reference somehow

if(/* person is instance of LawyerClass */)
{
  LawyerClass* lawyer = dynamic_cast<LawyerClass*>(person);
  lawyer->GoToCourt();
}
else
{
  DoctorClass* doctor = dynamic_cast<DoctorClass*>(person);
  doctor->GoToSurgery();
}

Update: if you want to use the subclass instances later, you can do it this way:

PersonClass* person = // get a Person reference somehow
...
LawyerClass* lawyer = NULL;
DoctorClass* doctor = NULL;

if(/* person is instance of LawyerClass */)
{
  lawyer = dynamic_cast<LawyerClass*>(person);
}
else if(/* person is instance of DoctorClass */)
{
  doctor = dynamic_cast<DoctorClass*>(person);
}
...
if(lawyer)
{
  lawyer->GoToCourt();
}
else if (doctor)
{
  doctor->GoToSurgery();
}

Note that this code is more complicated and more error-prone than the previous version. I would definitely try to refactor such code to make it look more like the previous version. YMMV.

Péter Török
This is exactly what I want, but I need to be able to use 'doctor' outside of the conditional but I can't since it is defined inside of the conditional. Is that possible?
David Doria
@David, see my update.
Péter Török
A: 

Is it possible for you to add a shim to the classes like this:

class Person
{
public:
    virtual void DoJob() = 0;
};

class Lawyer : public Person, public LawyerClass
{ 
public:
    virtual void DoJob()  { GoToCourt(); }
}; 

class Doctor : public Person, public DoctorClass
{ 
public:
    virtual void DoJob() { GoToSurgery(); }
}; 

void DoJob(Person& person)
{
    person.DoJob();
}

int main(int argc, char *argv[]) 
{
    Doctor doctor;
    Lawyer lawyer;
    DoJob(doctor); // Calls GoToSurgery();
    DoJob(lawyer); // Calls GoToCourt();
    return 0;
} 

This way, you won't have to use a conditional. But this really is a "last-resort" solution if you really can't change the existing library code, and it does require your users to use Doctor and Lawyer instead of DoctorClass and LawyerClass.

In silico
+1  A: 

If they are not polymorphic functions (as David's answer defined them to be), then doing this is going to be more than difficult.

I would suggest a wrapper class.

class PersonWrapper {
    PersonClass* person;
    virtual void DoWork() = 0;
};
class DoctorWrapper : public PersonWrapper {
    DoctorClass* doc;
    virtual void DoWork() { doc->GoToSurgery(); }
};
class LawyerWrapper : public PersonWrapper {
    LawyerClass* lawyer;
    virtual void DoWork() { lawyer->GoToCourt(); }
};

Of course, this leaves some implementation details to be defined by you, such as assigning the pointer in correct conditions, and is an ugly use of heap. However, it should offer polymorphic functionality, in that you could now do

PersonWrapper* wrap = new LawyerWrapper(new LawyerClass());
wrap->DoWork();

I would only consider using this sort of solution if you're genuinely desperate.

DeadMG