Creating a new object from dynamic type info
Asked Answered
Z

8

21

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?

Zilla answered 9/1, 2010 at 9:5 Comment(0)
R
12

Clone method

There is nothing provided by the language that queries type and lets you construct from that information, but you can provide the capability for your class hierarchy in various ways, the easiest of which is to use a virtual method:

struct Base {
  virtual ~Base();
  virtual std::auto_ptr<Base> clone(/*desired parameters, if any*/) const = 0;
};

This does something slightly different: clone the current object. This is often what you want, and allows you to keep objects around as templates, which you then clone and modify as desired.

Expanding on Tronic, you can even generate the clone function.

Why auto_ptr? So you can use new to allocate the object, make the transfer of ownership explicit, and the caller has no doubt that delete must deallocate it. For example:

Base& obj = *ptr_to_some_derived;
{ // since you can get a raw pointer, you have not committed to anything
  // except that you might have to type ".release()"
  Base* must_free_me = obj.clone().release();
  delete must_free_me;
}
{ // smart pointer types can automatically work with auto_ptr
  // (of course not all do, you can still use release() for them)
  boost::shared_ptr<Base> p1 (obj.clone());
  auto_ptr<Base>          p2 (obj.clone());
  other_smart_ptr<Base>   p3 (obj.clone().release());
}
{ // automatically clean up temporary clones
  // not needed often, but impossible without returning a smart pointer
  obj.clone()->do_something();
}

Object factory

If you'd prefer to do exactly as you asked and get a factory that can be used independently of instances:

struct Factory {}; // give this type an ability to make your objects

struct Base {
  virtual ~Base();
  virtual Factory get_factory() const = 0; // implement in each derived class
    // to return a factory that can make the derived class
    // you may want to use a return type of std::auto_ptr<Factory> too, and
    // then use Factory as a base class
};

Much of the same logic and functionality can be used as for a clone method, as get_factory fulfills half of the same role, and the return type (and its meaning) is the only difference.

I've also covered factories a couple times already. You could adapt my SimpleFactory class so your factory object (returned by get_factory) held a reference to a global factory plus the parameters to pass to create (e.g. the class's registered name—consider how to apply boost::function and boost::bind to make this easy to use).

Replay answered 9/1, 2010 at 9:15 Comment(6)
+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" en.wikipedia.org/wiki/Cloning_(programming)Choplogic
A good reference on creating Factories in C++ is 'Modern C++ Design' by Andrei Alexandrescu.Chlor
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.Replay
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.Emmittemmons
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.Replay
Please replace auto_ptr with unique_ptr or something else that's appropriate.Agateware
B
6

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.

Bonanno answered 9/1, 2010 at 10:28 Comment(6)
Good point, but you can change the dynamic_cast to a static_cast, as you know the inheritance.Replay
Using dynamic_cast catches cases where Derived is incorrectly defined (e.g. by copy&paste) that would cause UB otherwise. The penalty of dynamic cast is negligible compared to that of allocating memory and actually copying the object. Using dynamic_cast also allows the cast to work with multiple inheritance where static_cast could not be used. Those who know what they are doing can easily replace the cast with static_cast.Bonanno
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.Replay
class Triangle: public Shape_CRTP<Square> {};Bonanno
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.Bonanno
Ah, I was trying to copy across the hierarchy, which is caught at compile time (static_cast<Circle*>(&objOfTypeSquare) equivalent), and missed the obvious. Fair enough point there, but "don't pay for what you don't use" applies IMHO, I'll document the UB behavior possibility, and being familiar with CRTP will help prevent it.Replay
F
3

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 don't 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();
}
Frink answered 9/1, 2010 at 9:17 Comment(5)
Note you must be very careful with the order of your casts! Example: SubFoo (which inherits Foo) must come before Foo.Replay
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.Bonanno
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.Replay
@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".Apparent
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.Bonanno
C
1

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.)

Camphorate answered 9/1, 2010 at 9:26 Comment(0)
P
1

I used macros in my project to synthesize such methods. I'm just researching this approach now, so I may be wrong, but here's an answer to your question in my code of IAllocable.hh. Note that I use GCC 4.8, but I hope 4.7 suits.

#define SYNTHESIZE_I_ALLOCABLE \
    public: \
    auto alloc() -> __typeof__(this) { return new (__typeof__(*this))(); } \
    IAllocable * __IAllocable_alloc() { return new (__typeof__(*this))(); } \
    private:


class IAllocable {
public:
    IAllocable * alloc() {
        return __IAllocable_alloc();
    }
protected:
    virtual IAllocable * __IAllocable_alloc() = 0;
};

Usage:

class Usage : public virtual IAllocable {

    SYNTHESIZE_I_ALLOCABLE

public:
    void print() {
        printf("Hello, world!\n");
    }
};

int main() {
    {
        Usage *a = new Usage;
        Usage *b = a->alloc();

        b->print();

        delete a;
        delete b;
    }

    {
        IAllocable *a = new Usage;
        Usage *b = dynamic_cast<Usage *>(a->alloc());

        b->print();

        delete a;
        delete b;
    }
 }

Hope it helps.

Pistole answered 20/7, 2012 at 5:39 Comment(0)
K
0

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.....

Korrie answered 9/1, 2010 at 9:8 Comment(5)
This does not allow you to create a new object of that type.Replay
Can I use the value returned by typeid to create a new object of that type? I don't want to hardcode anything.Zilla
This just allows to know the exact type of that object.Korrie
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).Replay
Be careful with typeid().name() since it is implementation specific. Under g++ 4.0.1 this example prints: a is: P5CBase b is: P5CBase...Velamen
S
0
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);
}
Speechmaker answered 9/1, 2010 at 10:50 Comment(0)
R
0

When there are extremely many classes deriving from the same base class then this code will save you from having to include clone methods every class. It's a more convenient way of cloning that involves templates and an intermediate subclass. It's doable if the hierarchy is shallow enough.

struct PureBase {
    virtual PureBase* Clone() {
        return nullptr;
    };
};

template<typename T>
struct Base : PureBase {
    virtual PureBase* Clone() {
        return new T();
    }
};

struct Derived : Base<Derived> {};

int main() {
    PureBase* a = new Derived();
    PureBase* b = a->Clone(); // typeid(*b) == typeid(Derived)
}
Relaxation answered 11/8, 2017 at 21:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.