4

I'm getting confused why p->a() is calling B::a()?. Is there a paragraph somewhere in the C++ documentation/standard that describes this behavior well?

#include <iostream>
using namespace std;

class A {
  public:
  A() { cout << "A ctor" << endl; a_instance = this; }
  static A *get_instance() { return a_instance; }
  static A *a_instance;
  void virtual a() { cout << "From base class" << endl; }
};

class B : public A {
public:
  B() { cout << "B ctor" << endl; b_instance = this; }
  static B *get_instance() { return b_instance; }
  static B *b_instance;
  void virtual a() { cout << "From derived class" << endl; }
};

A *A::a_instance = 0;
B *B::b_instance = 0;

main()
{
    cout << "Create A" << endl;
    A ab;
    cout << "Create B" << endl;
    B abc;
    B *ptr = B::get_instance();
    A *p = A::get_instance();

    cout << "Called from A object type" << endl;
    if (p) {
       p->a();
    }
}
LihO
  • 41,190
  • 11
  • 99
  • 167
JohnX
  • 245
  • 3
  • 14

4 Answers4

6

When you create the variable abc, A's constructor sets a_instance to that instance. Despite p being a pointer to an A, since the instance is pointing to a B, it's correctly calling B::a().

To fix this behaviour, you could use the following:

A* A::get_instance()
{
    static A a;
    return &a;
}

B* B::get_instance()
{
    static B b;
    return &b;
}  

and remove all code that has to do with a_instance and b_instance.

Bart van Nierop
  • 4,130
  • 2
  • 28
  • 32
  • Thank you for copying my answer. – LihO Mar 25 '14 at 15:08
  • @LihO I'm sorry. I wasn't aware that improving an answer using a common technique such as described [here](http://stackoverflow.com/a/1008289/2700399) was considered copying. FWIW your answer is much more detailed, and posted while I had the edit window open, as I'm doing other things and working on SO while waiting on some slow tests. – Bart van Nierop Mar 25 '14 at 15:16
5

The B constructor calls the A constructor first. That replaces the a_instance that you'd already created.

This chaining of constructors is well defined in the standard. The base is always called first, so that the derived constructor is guaranteed to be working on a valid base object.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
4

What you are experiencing is caused by the design error, which is based on A's constructor initializing the static member using this. Body of this constructor is invoked not only when you are creating the instance of A but also when you are creating the instance of any of its derived classes:

A() { /* ... */ a_instance = this; }

So when you create an instance of type B, before the body of B's constructor is executed, the body of A's constructor is executed at first - this overwrites member a_instance with this within a context of instance of type B, i.e. it makes a_instance to point to this new instance of type B.


What you could do is to place a static variable inside of the getInstance method:

class A
{
public:
    static A* getInstance() {
        static A s;                 // <-- instantiated upon first call
        return &s;
    }
    void virtual foo() { std::cout << "From base class" << std::endl; }

protected:
    A() { }                         // <-- protected constructor
    ~A() { }
    A(const A&);                    // <-- protected copy constructor
    A& operator=(const A&);         // <-- protected assignment operator
};

then B:

class B : public A
{
public:
    static B* getInstance() {
        static B s;                 // <-- instantiated upon first call
        return &s;
    }
    void virtual foo() { std::cout << "From derived class" << std::endl; }

protected:
    B() { }                         // <-- protected constructor
    ~B() { }
    B(const B&);                    // <-- protected copy constructor
    B& operator=(const B&);         // <-- protected assignment operator
};

and possible usage:

int main() {
    A::getInstance()->foo();
    B::getInstance()->foo();
}

that outputs:

From base class
From derived class

LihO
  • 41,190
  • 11
  • 99
  • 167
  • Outstanding explanation!. Thanks!. – JohnX Mar 25 '14 at 15:09
  • @JohnX: You're welcome. Note that making copy constructor and assignment operator private ensures that there will be only 1 instance of this class. – LihO Mar 25 '14 at 15:15
2

B constructor invokes A constructor...

CiaPan
  • 9,381
  • 2
  • 21
  • 35