简体   繁体   中英

Is there a way to know in the superclass constructor the subclass of the calling object? C++

Let's suppose I have something like:

class A 
{
  public:
     A(A* owner)
     {
        m_owner=owner;
        if (dynamic_cast<B*>(this))
        {
          m_id=sm_bid; 
          sm_bid++;
        }
        else
        {
          m_id=sm_aid;
          sm_aid++;
        }
     }
  private:
    int m_id;
    A*  m_owner;
    static int sm_aid;
    static int sm_bid;
};

A::sm_aid=0;
A::sm_bid=0;

class B: public A
{
    B(A* owner) : A(owner) {}
};

Unfortunately, the dynamic_cast is unable to catch that it is a B object (when instantiated). This sound logical (since the constructor in the superclass is called before to call the constructor in the subclass. Is there a way to achieve such functionality?

Your design looks convoluted and almost certainly could be implemented without base needing to know it's derived.

However, it is the holy right to shoot oneself in the foot with C++ :) Therefore, you can try using Curiously Recurring Template Pattern (CRTP) to achieve your goals (polymorphic behavior in the constructor via static polymorphism).

Here is the quick and dirty sketch :

#include <iostream>
#include <type_traits>

struct Owner
{};

struct B;

template <class Derived>
struct A : public Owner
{
    A(Owner*)
    {
        if (std::is_same<Derived, B>::value)
            std::cout << "called from derived B" << std::endl;
        else
            std::cout << "not called from derived B\n" << std::endl;
    }
};

struct B : public A<B>
{
    B(Owner* owner) : A<B>(owner) {}
};

int main()
{
    A<void> a(nullptr);
    B b(&a);
}

Output:

not called from derived B
called from derived B

Note : I noticed that you are using static variables for counting. Please note that each template instantiation will have its own static variable!

Firstly, for dynamic_cast to work you need to have at least one virtual method in the base class (usually the destructor is made virtual since you often need that for other reasons).

Secondly, while constructing the A subobject, in A 's constructor all virtual functions will refer A 's class definition (even if you are in fact constructing A as a subobject of B ) and dynamic_cast will say that this is not a type B .

This is because the object is not an object of type B until the completion of B 's constructor, and so any and all checks to try to determine if it's a type B will say it's not because the object is in fact not a type B (yet).

I hope this clears up some confusion for you.

EDIT:
I just noticed the last paragraph under your question where you instead ask for a solution to this.

Without knowing more about the problem it will be difficult to really help you with achieving this functionality. My suggestion is to not do that, perhaps use another class altogether to assign id's to your objects rather than trying to have A do it.

If it really must be in A then perhaps this could be of help:

struct A
{
  template <class FinalType>
  A(A* owner, FinalType *){...}//use std::is_same to check if `B` or not
};

struct B : A
{
  B(A * owner):A(owner, this){}
};

If A is an abstract type you can use tag based overloading technique.

class B;
class C;

class A
{
public:
    template<class Derived>
    struct my_derived
    {
        using type = Derived;
    };

protected:
    explicit A(my_derived<B>)
    {
        // Do whatever you want when derived is B
    }

    explicit A(my_derived<C>)
    {
        // Do whatever you want when derived is C
    }

private:

};

class B : A
{
public:
    B() : A(A::my_derived<B>())
    {
    }
};

class C : A
{
public:
    C() : A(A::my_derived<C>())
    {
    }
};

Also another approach would be template based programming as AMA suggest. But Although Templates are powerful feature in C++ they have their own problems. They can't be implemented in a cpp file. They cause code bloat that can affect performance due to bigger code size that will cause higher rate of instruction cache miss.

So I think this approach is better than RTTI that has run time performance impact and you have to declare one virtual function in your base class, and also template based approach.

It seems like this works just fine:

#include <iostream>
#include <type_traits>

class Owner
{};

class B;

template <class Derived>
class A : public Owner
{
    public:
    A(Owner* owner)
    {
        m_owner=owner;
        if (std::is_same<Derived, B>::value)
            std::cout << "called from derived B" << std::endl;
        else
            std::cout << "not called from derived B\n" << std::endl;
    }
    private:
    Owner* m_owner;
};

class B : public A<B>
{
    public:
    B(Owner* owner) : A<B>(owner)
    {}
};

int main()
{
    A<void> a(nullptr);
    B b(&a);
}

Kudos to AMA. Anything can be done in C++

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM