tags:

views:

356

answers:

6

In C++, is there any way to query the type of an object and then use that information to dynamically create a new object of the same type?

For example, say I have a simple 3 class hierarchy:

class Base
class Foo : public Base
class Bar : public Base

Now suppose I give you an object cast as type Base -- which is in reality of type Foo. Is there a way to query the type and use that info to later create new objects of type Foo?

A: 

In C++, is there any way to query the type of an object...

Yes, use typeid() operator

For example:

// typeid, polymorphic class
 #include <iostream>
 #include <typeinfo>
 #include <exception>
 using namespace std;

 class CBase { virtual void f(){} };
 class CDerived : public CBase {};

 int main () {
   try {
     CBase* a = new CBase;
     CBase* b = new CDerived;
      cout << "a is: " << typeid(a).name() << '\n';
     cout << "b is: " << typeid(b).name() << '\n';
     cout << "*a is: " << typeid(*a).name() << '\n';
     cout << "*b is: " << typeid(*b).name() << '\n';
    } catch (exception& e) { cout << "Exception: " << e.what() << endl; }
    return 0;
  }

Output:

a is: class CBase *
b is: class CBase *
*a is: class CBase
*b is: class CDerived

If the type typeid evaluates is a pointer preceded by the dereference operator (*), and this pointer has a null value, typeid throws a bad_typeid exception

Read more.....

Prasoon Saurav
This does not allow you to create a new object of that type.
Roger Pate
Can I use the value returned by typeid to create a new object of that type? I don't want to hardcode anything.
Daniel
This just allows to know the exact type of that object.
Prasoon Saurav
Also note the names returned by `name()` are implementation-specific, and may not be terribly useful (though you can use an implementation-specific demangling function too).
Roger Pate
Be careful with `typeid().name()` since it is implementation specific. Under g++ 4.0.1 this example prints: `a is: P5CBase b is: P5CBase...`
D.Shawley
+3  A: 
Roger Pate
+1, yip. Note that the term "clone" is not an arbitrary one... it is what people have standardized on for describing this pattern, and you can find relevant resources and discussions of the issue by searching on "C++ clone object" http://en.wikipedia.org/wiki/Cloning_(programming)
Hostile Fork
A good reference on creating Factories in C++ is 'Modern C++ Design' by Andrei Alexandrescu.
pau.estalella
auto_ptr is evil!
Kornel Kisielewicz
No, it's perfect here (represents *new* was used, in particular, so you know *delete* is required if the pointer is released), and it's in the current C++ stdlib. You cannot say the same about anything else (currently). Use release() to put the pointer into any other smart pointer type you like.
Roger Pate
Using auto_ptr kind of commits you to using it in the remainder of your code (or write some unecessary .release() stuff). A general purpose class should return a plain pointer, which the user of the class can store in whatever type of smart pointer they choose.
anon
How are you committed to using it in any other way than typing ".release()"? Is that really such a huge burden? --- It's actually a benefit, as it explicitly represents the transfer of ownership. --- And with auto_ptr you can still use whatever type of smart pointer you choose, or none at all.
Roger Pate
+3  A: 

There's only some hacky ways to do this.

The first and IMHO the ugliest is:

Base * newObjectOfSameType( Base * b )
{
  if( dynamic_cast<Foo*>( b ) ) return new Foo;
  if( dynamic_cast<Bar*>( b ) ) return new Bar;
}

Note that this will only work if you have RTTI enabled and Base contains some virtual function.

The second an neater version is to add a pure virtual clone function to the base class

struct Base { virtual Base* clone() const=0; }
struct Foo : public Base { Foo* clone() const { return new Foo(*this); }
struct Bar : public Base { Bar* clone() const { return new Bar(*this); }

Base * newObjectOfSameType( Base * b )
{
  return b->clone();
}

This is much neater.

One cool/interesting thing about this is that Foo::clone returns a Foo*, while Bar::clone returns a Bar* . You might expect this to break things, but everything works due to a feature of C++ called covariant return types.

Unfortunately covariant return types dont work for smart pointers .. so using sharted_ptrs your code would look like this.

struct Base { virtual shared_ptr<Base> clone() const=0; }
struct Foo : public Base { shared_ptr<Base> clone() const { return shared_ptr<Base>(new Foo(*this) ); }
struct Bar : public Base { shared_ptr<Base> clone() const { return shared_ptr<Base>(new Bar(*this)); }

shared_ptr<Base> newObjectOfSameType( shared_ptr<Base> b )
{
  return b->clone();
}
Michael Anderson
Note you must be very careful with the order of your casts! Example: SubFoo (which inherits Foo) must come before Foo.
Roger Pate
Not very good answers. The first option is just crap (and the code is missing a return/throw if nothing matches). While returning Derived* is a nice trick, there is little use in it because it still won't make Base::clone return the right types. Still, the second option is what everybody normally does, usually with Base* return types. The third option is just unnecessary and nasty because it enforces the use of shared_ptrs. It is possible to do <code>shared_ptr<Base> ptr(new Derived());</code> but it is better to avoid the entire problem by returning Base* (or auto_ptr<Base>) instead.
Tronic
Tronic: Covariant return types are useful, because they *preserve* static type information when you already have it. (Even though, as you said, they provide no benefit when you don't have it.) An example from the stdlib is fstream::rdbuf (however, that has its own problems..). However, I strongly prefer self-documenting when returning pointers which must be released, and that means a smart pointer type like auto_ptr for this pattern, instead of covariant return types, anyway.
Roger Pate
@Tronic: The advantage of the first method is that it is non-intrusive, and it works well for a known set of objects. Regarding the missing return: the reader is advised to understamd rather than to copy the solution. --- what type do you expect base::clone to return? That argument is almost as week as calling the examples "crap" or "nasty".
peterchen
Good point about it being non-intrusive. I completely overlooked that. The argument about return types is IMO quite relevant because you would not normally call derivedPtr->clone() at all, but instead it is (nearly) always used via base pointer. As derived pointers cause issues with smart pointers (returning auto_ptr<Derived>, or using assignment syntax for initialization of any smart pointer), it doesn't seem like a good solution.
Tronic
+1  A: 

You can use e.g. typeid to query an object's dynamic type, but I don't know of a way to directly instantiate a new object from the type information.

However, apart from the clone approach mentioned above, you could use a factory:

#include <typeinfo>
#include <iostream>

class Base
{
public:
    virtual void foo() const
    {
        std::cout << "Base object instantiated." << std::endl;
    }
};


class Derived : public Base
{
public:
    virtual void foo() const
    {
        std::cout << "Derived object instantiated." << std::endl;
    }
};


class Factory
{
public:
    static Base* createFrom( const Base* x )
    {
        if ( typeid(*x) == typeid(Base) )
        {
            return new Base;
        }
        else if ( typeid(*x) == typeid(Derived) )
        {
            return new Derived;
        }
        else
        {
            return 0;
        }
    }
};


int main( int argc, char* argv[] )
{
    Base* X = new Derived;
    if ( X != 0 )
    {
        std::cout << "X says: " << std::endl;
        X->foo();
    }

    Base* Y = Factory::createFrom( X );
    if ( Y != 0 )
    {
        std::cout << "Y says: " << std::endl;
        Y->foo();
    }

    return 0;
}

P.S.: The essential part of this code example is of course the Factory::createFrom method. (It's probably not the most beautiful C++ code, since my C++ has gone a little rusty. The factory method probably shouldn't be static, on second thought.)

stakx
+4  A: 

The commonly used way to create copies of objects by base class is adding a clone method, which is essentially a polymorphic copy constructor. This virtual function normally needs to be defined in every derived class, but you can avoid some copy&paste by using the Curiously Recurring Template Pattern:

// Base class has a pure virtual function for cloning
class Shape {
public:
    virtual ~Shape() {} // Polymorphic destructor to allow deletion via Shape*
    virtual Shape* clone() const = 0; // Polymorphic copy constructor
};
// This CRTP class implements clone() for Derived
template <typename Derived> class Shape_CRTP: public Shape {
public:
    Shape* clone() const {
        return new Derived(dynamic_cast<Derived const&>(*this));
    }
};
// Every derived class inherits from Shape_CRTP instead of Shape
// Note: clone() needs not to be defined in each
class Square: public Shape_CRTP<Square> {};
class Circle: public Shape_CRTP<Circle> {};
// Now you can clone shapes:
int main() {
    Shape* s = new Square();
    Shape* s2 = s->clone();
    delete s2;
    delete s;
}

Notice that you can use the same CRTP class for any functionality that would be the same in every derived class but that requires knowledge of the derived type. There are many other uses for this besides clone(), e.g. double dispatch.

Tronic
Good point, but you can change the dynamic_cast to a static_cast, as you know the inheritance.
Roger Pate
Tronic
@Tronic: Good work man.
Jagannath
Can you construct such a UB case? static_cast is not the same as reinterpret_cast (which would cause UB easily). I also cannot construct a case of MI where static_cast is ambiguous but dynamic_cast is not.
Roger Pate
class Triangle: public Shape_CRTP<Square> {};
Tronic
About MI, you are right, the limitations do not apply here, as no casting between branches of the hierarchy or from virtual bases is needed.
Tronic
Roger Pate
A: 
class Base
{
public:
 virtual ~Base() { }
};

class Foo : public Base
{

};

class Bar : public Base
{

};

template<typename T1, typename T2>
T1* fun(T1* obj)
{
 T2* temp = new T2();
 return temp;
}

int main()
{
  Base* b = new Foo();
  fun<Base,Foo>(b);
}
Jagannath