Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Type of an object changing during construction

I just discoverd the following behaviour : having an object of type B derived from type A, the final type during the construction of A is A and not B. This can be observed with the following example :

#include <iostream>
#include <typeinfo>

class A
{
    public:
        A() { std::cout << &typeid(*this) << std::endl; }
};

class B : public A
{
    public:
        B() : A() { std::cout << &typeid(*this) << std::endl; }
};

int main()
{
    A a;
    B b;
    return 0;
}

A run of this code (compiled with gcc 4.8.5) is the following :

0x400ae0
0x400ae0
0x400ac0

We can see that the type returned by typeid in A::A() is A and not B, and then the final type changes to become B.

Why ?

Is it possible to know the "real" final type during the construction of the parent class ?

My context is the following :

I have a parent class Resource and several classes inheriting from it. I also have a ResourceManager notified by each creation of a resource, and having to know the final type of the created resource. What I'm doing to avoid duplicated code is the following, but it doesn't work :

class Resource
{
  public:
    Resource() { ResourceManager::notifyCreation(*this); }
    ~Resource() { ResourceManager::notifyDestruction(*this); }
};
class MyResource : public Resource
{
  // I don't have to care to the manager here
};

I know I can do the notification in each constructor/destructor of the children, but it's less robust (possible bug if a resource is instanciated without notification to the manager). Have you any idea for a workaround ?

like image 773
Caduchon Avatar asked Jul 14 '17 13:07

Caduchon


2 Answers

Sounds like what you're looking for is CRTP

template<typename Concrete>
struct Resource
{
    Resource() { ResourceManager::notifyCreation(*static_cast<Concrete*>(this)); }
    ~Resource() { ResourceManager::notifyDestruction(*static_cast<Concrete*>(this)); }
};

struct MyResource : Resource<MyResource>
{

};

Note that MyResource is not yet finished constructed when the call to notifyCreation is made. The address of the MyResource instance can be taken, but that's about all that can be done to the instance. (Thanks to Caleth for pointing this out)

In particular from [class.cdtor]

If the operand of typeid refers to the object under construction or destruction and the static type of the operand is neither the constructor or destructor's class nor one of its bases, the behavior is undefined.

Therefore ResourceManager would have to be implemented somewhat like this to enable using typeid

struct ResourceManager
{
    template<typename T>
    void notifyCreation(T&&)
    {
        add(typeid(T));  // can't apply to an expression
    }
    template<typename T>
    void notifyDestruction(T&&)
    {
        remove(typeid(T));  // can't apply to an expression
    }
};
like image 67
Passer By Avatar answered Nov 15 '22 06:11

Passer By


There is no good way to do it in a constructor as in your example, but you can provide a special constructor for A, i.e.

 A(const std::type_info &info) {
    std::cout << info.name() << std::endl;
 }

and in B

B() : A(typeid(*this)) {
    std::cout << typeid(*this).name() std::endl;
}

if you do it outside the constructor, you can also provide a virtual function in 'A' and overwrite it in 'B'.

like image 20
Serge Avatar answered Nov 15 '22 06:11

Serge