Solved

Forward references and static class member instances !

Posted on 2003-11-16
9
625 Views
Last Modified: 2010-04-01
I am playing with forward references and am having trouble figuring out why the following code compiles/links and other code will not compile.

Firstly, the code that compiles:

/////////////////////////////////////////

#include <iostream>

using namespace std;

class B
{
public:
      B() { cout << "B constructor" << endl;}
};

class A
{
public:
      A() { cout << "A constructor" << endl;}
private:
      static B classBInstance;
      B a;
};


//B A::classBInstance;

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


/////////////////////////////////////////

Right then, two classes stripped to the bare bones.  I have declared A to hold a static member of a class B instance, yet have commented out the part that actually initialises it.  

Output:

B constructor
A constructor

Why does the code still compile and run ?  I should be getting a linker error at the lack of A::classBInstance should I not ??  And why is the output showing that the B member (the none static one) is being constructed before the A object itself ?

Secondly,

/////////////////////////////////////////

#include <iostream>

using namespace std;

class B;

class A
{
public:
      A() { cout << "A constructor" << endl;}
private:
      B a; <-----------------
};

class B
{
public:
      B() { cout << "B constructor" << endl;}
};

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

/////////////////////////////////////////

I have set up a forward reference to class B, as can be seen, yet the code as-is still fails to compile, moaning about line <-- with:

'a' uses undefined class 'B'

Yet if I change the <-- line to be

B a();

i.e. add parentheses, or move the whole of class B to be above class A, there is no complaint.  Why are the parentheses needed ?  And what is the correct syntax for a forward reference of this type ?
0
Comment
Question by:mrwad99
  • 3
  • 2
  • 2
  • +1
9 Comments
 
LVL 3

Expert Comment

by:monkesdb
ID: 9758995
as for your question i'm not at all sure.

B a;       ' This should define a as being of type B

B a();     ' Whereas, this should define a as being of type B and construct it.

so if one of them was not to work I would have thought the second wouldn't

But
it would probably be better to use pointers. With pointers there's no problem what so ever since it doesn't need to know the size of the class to create a pointer to it. and it works when A creates a B and B creates an A (circular reference).

class A
{
public:
     A() : { cout << "A constructor" << endl;}
private:
     B* b;
};

class B
{
public:
     B() : { cout << "B constructor" << endl;}
private:
     A* a;
};

int main()
{
     A a;
     return 0;
}
0
 
LVL 19

Author Comment

by:mrwad99
ID: 9759018
Yeah thanks, but I am only interested as to why it does not work without pointers.

Also the example does not work without forward reference to either of A or B, depending on the order they are coded in (I used forward ref to B to make it work).  Also no need for the : in the constructors.  Using MSVC++ 6.0SP3

Ta anyway :)
0
 
LVL 11

Expert Comment

by:bcladd
ID: 9759315
It doesn't work in the second case because if you declare a member of type B the compiler must add the size of the member to the size of the class but ALL it knows about B at that point is that you have promised that it is a classs somewhere defined. Thus putting int a B* will fix the problem.

B a; // requires size of B to instanitate (full definition required)

B a(): // defines a FUNCTION called a that takes no parameters and returns a B

B * a; // B is a type so this is safe; to USE a inside any member of the class you'll need the full definition

Your first question is related to this answer: The object member (non-static) is an object so the constructor is still called. The static member is NOT constructed and no memory is set asiide for it BUT it is only an undefined symbol if someone references it (excuse the anthropomorphism). So only one B is built, the one built when the A is instantiated in main.

Hope this helps, -bcl
0
 

Expert Comment

by:marcon33
ID: 9759460
//B A::classBInstance;

This creates another instance of A, it is not necessary. Putting is outside of main is sort of like declaring a function. The compiler says, OK. If it is not there it is OK too.


Objects are constructed by the compiler without you having to initialize with a line like the above. They are also "destructed" in the background if you do not provide a destructor.
It seems that I read somewhere that the order of object creation and destruction has to do with keeping things simple. B is created , Then A is created, B is destroyed then A is destroyed. If it were the opposite and you were concerned about compiler complexity and speed, create A , create B, destroy A but first check if other objects were created first, then do that again, and again.......

You can watch what is happening with the code below :

#include <iostream>

using namespace std;

class B
{
public:
     B() { cout << "B constructor" << endl;}
       ~B();
};

B::~B()
{
      cout << " B destroyed" << endl;
}


class A
{
public:
     A() { cout << "A constructor" << endl;}
       ~A();


private:
     static B classBInstance;
     B a;
};

 A::~A()
{
      cout << " A destroyed" << endl;
}  

include <iostream>
#include  "TestExp.h"

using namespace std;

B A::classBInstance;

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


Hope that helps1
0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 19

Author Comment

by:mrwad99
ID: 9759555
Right,

Sorry about the delay, I was scrutinizing what was said and then applying it in my code.

marcon33 -

>//B A::classBInstance;
>This creates another instance of A, it is not necessary.

I am lost.  Whenever I have had a class containing a static member it has always been initialised in the body of the class, i.e. the .cpp file.  I am not sure at all as to what you are saying here.  Can you elaborate ?

bcladd -

1) I am still not clear as to why defining an object to have a pointer to a type that has not yet been fully defined is allowed, yet having an actual object is not allowed.  Can anyone clarify this further ?  (sorry).

2)  Regarding the static variable, I have changed my code to:

//////////////////////////////////////

#include <iostream>

using namespace std;

class B
{
public:
    B() { cout << "B constructor" << endl;}
    void F() { cout << "B::F()" << endl; }
};


class A
{
public:
    A() { cout << "A constructor" << endl;}
    static B b;
};

B A::b;

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

//////////////////////////////////////

Now, without the B A::b definiton, the code will not link, stating that it cannot find a.b.F().  This is because I am trying to call the method of an object, but the object of does not exist,  correct ?

2)  Code change again:

//////////////////////////////////////

#include <iostream>

using namespace std;

class B
{
public:
      B(int) { cout << "B(int) constructor" << endl; }
        B() { cout << "B constructor" << endl;}
      void F() { cout << "B::F()" << endl; }
private:    
};


class A
{
public:
          A() { cout << "A constructor" << endl;}
          static B b;
};


B A::b(1000);

int main()
{
      cout << "Creating A" << endl;
        A a;
      cout << "Creating B" << endl;
      B b;
      cout << "Calling method..." << endl;
      a.b.F();
        return 0;
}


OUTPUT:

B(int) constructor (i)
Creating A
A constructor (ii)
Creating B
B constructor (iii)
Calling method...
B::F()

//////////////////////////////////////

Now, with (i) it appears that the compiler is constructing the static object before I have even created a B object !  What ?!?!?
(ii) is obviously down to the creation of a, the A object, but (iii) - why is the default B constructor being called *here* (it looks like it has already constructed the static object, hence (i) ), and why is the default one anyway ?!?! I have defined it to be a B object taking 1000 as a parameter after all !

Cheers again all !
0
 
LVL 11

Accepted Solution

by:
bcladd earned 40 total points
ID: 9759665
(1) A reference's or a pointer's size can be determined without knowing the size of the object refered/pointed to. Why? Two reasons: the standard says so (sorry, my standard is not at home so I can't give the reference) and it is practical to do so (most computers have a single size for addresses which is what a reference/pointer stores).

So, without knowing what class Q looks like, it is possible to tell the compiler that there is such a class (the forward reference) and to declare a pointer at one:

    class Q;
    class R {
        Q * myQ;
    };

The compiler knows that it should set aside enough room to hold a pointer at a Q. It is not possible to in-line the body of any function that calls any members using myQ, however, because the compiler knows only an incomplete type until the full definition of class Q is provided.

(2) marcon33: You say the following:

    //B A::classBInstance;

    This creates another instance of A, it is not necessary. Putting is outside of main is sort of like declaring a function. The compiler says, OK. If it is not there it is OK too.

and it is incorrect. Uncommenting the line will NOT create an A. All it does is have the compiler set aside the space for an object of type B which has the name A::classBInstance (it is an almost standard global variable declaration; only the name looks weird). When a data member is declared static in a class no space is actually set aside to store the one value for the class. It requires a single external delcaration such as the one that is commented out here.

(3) Static object are created BEFORE execution of your main program begins (assuming that they are properly declared outside the class itself). Thus in the first example, with the commented-out line restored, the output is
    B constructor - Static object constructed BEFORE main runs
    B constructor - Member of A that is being constructed. Default constructor for class type data member called
    A constructor - Constructor body for class A runs

(4) Look at your final code to answer your questions:

    B A::b(1000); // (i)

    int main()
    {
         cout << "Creating A" << endl;
         A a; // (ii)
         cout << "Creating B" << endl;
         B b; // (iii)
         cout << "Calling method..." << endl;
         a.b.F();
        return 0;
    }

    OUTPUT:

    B(int) constructor (i)
    Creating A
    A constructor (ii)
    Creating B
    B constructor (iii)
    Calling method...
    B::F()

Hope this helps, -bcl
0
 

Expert Comment

by:marcon33
ID: 9759938
ooops in over my head on this one. bcladd and mrwadd99, I appreciate your indulgence.
0
 
LVL 3

Expert Comment

by:monkesdb
ID: 9760609
(sorry about the colons they shouldn't be there i started initialising stuff and decided it was a bad idea so removed it but forgot the colons)
0
 
LVL 19

Author Comment

by:mrwad99
ID: 9766354
Once again, an outstanding answer from bcladd.  The standard of the experts here is jaw droppingly good, and really is an inspiration !

Many thanks to all !
0

Featured Post

Better Security Awareness With Threat Intelligence

See how one of the leading financial services organizations uses Recorded Future as part of a holistic threat intelligence program to promote security awareness and proactively and efficiently identify threats.

Join & Write a Comment

Errors will happen. It is a fact of life for the programmer. How and when errors are detected have a great impact on quality and cost of a product. It is better to detect errors at compile time, when possible and practical. Errors that make their wa…
What is C++ STL?: STL stands for Standard Template Library and is a part of standard C++ libraries. It contains many useful data structures (containers) and algorithms, which can spare you a lot of the time. Today we will look at the STL Vector. …
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn how to user default arguments when defining functions. This method of defining functions will be contrasted with the non-default-argument of defining functions.

746 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now