-1

Consider the following program:

class Base
{
private:
    int m_nID;
public:
    Base()
    {
        m_nID = ClassID();
    }

    // ClassID returns a class-specific ID number
    virtual int ClassID() { return 1; }

    int GetID() { return m_nID; }
};

class Derived: public Base
{
public:
    Derived()
    {
    }

    virtual int ClassID() { return 2; }
};

int main()
{
    Derived cDerived;
    cout << cDerived.GetID(); 
    return 0;
}

In the above example,The derived id is surprisingly 1 instead of 2.I have already found a similar question regarding the same question here.But what i don't understand is that if this is wrong,Then how are we supposed to identify a (derived) class member and use it?I mean suppose i want to dedicate a unique id or typename to each class (base class, the first derived class , the second derived class which is either a derivation of the base class or the second derived class and so on),In this regard how can i go on and act? I think the correct way is that i assign an id/name in the constructor upon the instantiation of any class object so that the type is known immediately.The above approach failes,What other options do i have in this regard?

Community
  • 1
  • 1
Hossein
  • 24,202
  • 35
  • 119
  • 224
  • 2
    after construction, `ClassID` returns what you need. – Karoly Horvath Aug 23 '12 at 20:17
  • possible duplicate of [Calling virtual functions inside constructors](http://stackoverflow.com/questions/962132/calling-virtual-functions-inside-constructors) – Bo Persson Aug 23 '12 at 20:46
  • @BoPersson:Actually the question is pretty much different.I am not asking why it is happening,I am asking how can i get what i intent to achieve using this approach by other approaches. – Hossein Aug 23 '12 at 21:16
  • @karolyHarvath:What if i am using a situation like this : void Foo(baseClass bar); and inside the Foo() function , based on the objects type, i have different operations.Suppose i have a Person class and two derived sub-classes named :Male and Female.There is a function such as this: void Foo(Person person){ if ( person.ClassID == 1 ) cout<<((Male)person).Type; else cout<<((Female)person).Type; } So does your solution work in this matter as well? – Hossein Aug 23 '12 at 21:17

3 Answers3

3

To elaborate on what Karoly said, maybe you can just avoid using the class ID in the constructor, but after construction you can just do:

cout << cDerived.ClassID();

There is no reason to have two functions that return the same thing and there is no reason to waste memory by having your int m_nID; stored in every object.

Also, you should change your base class so that it says:

virtual int ClassID() = 0;

This should make it a compiler error if you try to call ClassID in your Base constructor, though I have not tried it. Also, it will make Base be an abstract class, so you can't create new instances of it (which is good).

David Grayson
  • 84,103
  • 24
  • 152
  • 189
  • Thank you. What if i am using a situation like this : void Foo(baseClass bar); and inside the Foo() function , based on the objects type, i have different operations.Suppose i have a Person class and two derived sub-classes named :Male and Female.There is a function such as this: void Foo(Person person){ if ( person.ClassID == 1 ) cout<<((Male)person).Type; else cout<<((Female)person).Type; } So does your solution work in this matter as well? – Hossein Aug 23 '12 at 21:14
3

The answer is simple: You can't use any C++ object until it has been constructed. If you're still in the process of constructing the base-class subobject, then you can't use the derived-class object because it hasn't been constructed yet. (The vptr for the object currently under construction still points to the base-class vtable as long as you're inside the base-class constructor; it isn't updated until you get to the derived-class constructor.)

But then how does the base-class constructor determine whether it's being invoked for a regular object or a subobject? Well, the same way it tells any other random bit of information about the world: you have to tell it explicitly. For example:

struct Base {
    Base() { puts("I am a whole object!"); }
  protected:
    Base(bool is_subobject) { assert(is_subobject); puts("I'm only part of an object."); }
};

struct Derived : Base {
    Derived(): Base(/*is_subobject=*/true) { }
};

If you want to be really clever-trousers about it, you can use template metaprogramming:

struct Base {
    int m_nID;

    template<typename T>
    Base(T *)
    {
#ifdef UNSAFE
        // This breaks a lot of rules, but it will work in your case.
        // "this" is a Base, not a Derived, so the cast is undefined;
        // and if ClassID tried to use any non-static data members,
        // you'd be hosed anyway.
        m_nID = reinterpret_cast<T*>(this)->T::ClassID();
#else
        // This version is guaranteed to work in standard C++,
        // but you lose that nice virtual-ness that you worked
        // so hard for.
        m_nID = T::staticClassID();
#endif
    }

    virtual int ClassID() { return 1; }
    static int staticClassID() { return 1; }
    int GetID() { return m_nID; }
};

struct Derived : Base {
    Derived(): Base(this) { }
    virtual int ClassID() { return 2; }
    static int staticClassID() { return 2; }
};

int main() {
    Derived cDerived;
    std::cout << cDerived.GetID() << std::endl; // prints "2"
}

But as Dave S said while I was composing this answer... if your example is all you're doing, then you could just have a protected Base constructor that takes int nID as a parameter, and forget about the virtual methods altogether.

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • Please note that while it is the motivation behind that rule, the vtable/vptr is just an implementation detail. – PlasmaHH Aug 23 '12 at 20:38
  • 1
    Correct, vtable/vptr is just an implementation detail. But (as you can tell from my phrasing of the first sentence) I don't believe the rule "you can't access Derived from Base's constructor" is *motivated by* that particular implementation: instead it's motivated by the abstract notion of what it means to "construct" an object. From the implementation's POV, it would be simpler to set the vptr just once in `Derived::Derived`, but for *strictly theory-motivated* reasons the Standard requires that the implementation first set it to Base's vtable and then later reset it to Derived's. – Quuxplusone Aug 23 '12 at 21:32
2

A Derived object contains a sub-object of type Base, the Base object exists "inside" the Derived object. Literally inside it. If you take the address of the object and the address of its Base sub-object the Base will be somewhere within the memory region occupied by the Derived object.

When you construct a Derived its constructor runs, and the first thing that happens is it constructs each of its base classes then each of its members. So the first thing that happens when Derived::Derived() starts to execute is that Base::Base() executes. During that constructor, the object's dynamic type is not yet a Derived because it hasn't yet constructed the Derived parts. So when you call a virtual function during the Base constructor it finds the final overrider of the only object that has been constructed so far: the Base part.

Under the hood what happens is that when the Base constructor starts it sets the object's vptr to point to the vtable for Base, so it points to Base's virtual functions. After that completes and the Derived object constructor runs it updates the vptr to point to Derived's vtable, so it refers to the functions that Derived overrides. So until the Base constructor has finished the object's vptr only leads to pointers to the virtual functions defined by Base.

Once the Derived constructor has updated the vptr, calling the virtual functions will call the Derived overrides, so the answer to you question is to re-assign m_nId in the derived constructor, when it will call the overridden function and give you the derived class ID.

Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521