At first I got intrigued by this question as it looked like something really tricky to do, and all the comments about templates, dependencies and includes made sense. But then I tried to actually implement this and found it surprisingly easy. So either I misunderstood the question or the question has some special property of looking much harder than it really is. Anyway, here is my code.
This is the glorified autoptr.h:
#ifndef TESTPQ_AUTOPTR_H
#define TESTPQ_AUTOPTR_H
template<class T> class AutoPtr {
private:
T *p;
public:
AutoPtr() {p = new T();}
~AutoPtr() {delete p;}
T *operator->() {return p;}
};
#endif // TESTPQ_AUTOPTR_H
Looks really simple and I wondered if it actually works, so I made a test case for it. Here is my b.h:
#ifndef TESTPQ_B_H
#define TESTPQ_B_H
class B {
public:
B();
~B();
void doSomething();
};
#endif // TESTPQ_B_H
And b.cpp:
#include <stdio.h>
#include "b.h"
B::B()
{
printf("B::B()\n");
}
B::~B()
{
printf("B::~B()\n");
}
void B::doSomething()
{
printf("B does something!\n");
}
Now for the A class that actually uses this. Here's a.h:
#ifndef TESTPQ_A_H
#define TESTPQ_A_H
#include "autoptr.h"
class B;
class A {
private:
AutoPtr<B> b;
public:
A();
~A();
void doB();
};
#endif // TESTPQ_A_H
And a.cpp:
#include <stdio.h>
#include "a.h"
#include "b.h"
A::A()
{
printf("A::A()\n");
}
A::~A()
{
printf("A::~A()\n");
}
void A::doB()
{
b->doSomething();
}
Ok, and finally the main.cpp that uses A, but doesn't include "b.h":
#include "a.h"
int main()
{
A a;
a.doB();
}
Now it actually compiles with no single error nor warning and works:
d:\alqualos\pr\testpq>g++ -c -W -Wall b.cpp
d:\alqualos\pr\testpq>g++ -c -W -Wall a.cpp
d:\alqualos\pr\testpq>g++ -c -W -Wall main.cpp
d:\alqualos\pr\testpq>g++ -o a a.o b.o main.o
d:\alqualos\pr\testpq>a
B::B()
A::A()
B does something!
A::~A()
B::~B()
Does that solve your problem or am I doing something completely different?
EDIT 1: Is it standard or not?
Okay, it seems it was the right thing, but now it leads us to other interesting questions. Here is the result of our discussion in the comments below.
What happens in the example above? The a.h file doesn't need b.h file because it doesn't actually do anything with b
, it just declares it, and it knows its size because the pointer in the AutoPtr class is always the same size. The only parts of autoptr.h that need the definition of B are constructor and destructor but they aren't used in a.h so a.h doesn't need to include b.h.
But why exactly a.h doesn't use B's constructor? Aren't B's fields initialized whenever we create an instance of A? If so, the compiler may try to inline this code at every instantiation of A, but then it will fail. In the example above, it looks like the B::B()
call is put at the beginning of the compiled constructor A::A()
in the a.cpp unit, but does the standard require it?
At first it seems that nothing stops the compiler from inlining fields initialization code whenever an instant is created, so A a;
turns into this pseudocode (not real C++ of course):
A a;
a.b->B();
a.A();
Could such compilers exist according to the standard? The answer is no, they couldn't and the standard has nothing to do with it. When the compiler compiles "main.cpp" unit, it has no idea what A::A() constructor does. It could be calling some special constructor for b
, so inlining the default one before it would make b
initialized twice with different constructors! And the compiler has no way to check for it since the "a.cpp" unit where A::A()
is defined is compiled separately.
Okay, now you may think, what if a smart compiler wants to look at B's definition and if there is no other constructor than the default one, then it would put no B::B()
call in the A::A()
constructor and inline it instead whenever the A::A()
is called. Well, that's not going to happen either because the compiler has no way to guarantee that even if B doesn't have any other constructors right now, it won't have any in the future. Suppose we add this to b.h in the B class definition:
B(int b);
Then we put its definition in b.cpp and modify a.cpp accordingly:
A::A():
b(17) // magic number
{
printf("A::A()\n");
}
Now when we recompile a.cpp and b.cpp, it will work as expected even if we don't recompile main.cpp. That's called binary compatibility and the compiler shouldn't break that. But if it inlined the B::B()
call, we end up with main.cpp that calls two B
constructors. But since adding constructors and non-virtual methods should never break binary compatibility, any reasonable compiler shouldn't be allowed to do that.
The last reason for such compilers to not exist is that it doesn't actually make any sense. Even if the members initialization is inlined, it would just increase the code size and will give absolutely no performance increase since there still would be one method call for A::A()
so why not to let this method do all the work in one place?
EDIT 2: Okay, what about inline and auto-generated constructors of A?
Another question that arises is what will happen if we remove A:A()
from both a.h and a.cpp? Here's what happens:
d:\alqualos\pr\testpq>g++ -c -W -Wall a.cpp
d:\alqualos\pr\testpq>g++ -c -W -Wall main.cpp
In file included from a.h:4:0,
from main.cpp:1:
autoptr.h: In constructor 'AutoPtr<T>::AutoPtr() [with T = B]':
a.h:8:9: instantiated from here
autoptr.h:8:16: error: invalid use of incomplete type 'struct B'
a.h:6:7: error: forward declaration of 'struct B'
autoptr.h: In destructor 'AutoPtr<T>::~AutoPtr() [with T = B]':
a.h:8:9: instantiated from here
autoptr.h:9:17: warning: possible problem detected in invocation of delete
operator:
autoptr.h:9:17: warning: invalid use of incomplete type 'struct B'
a.h:6:7: warning: forward declaration of 'struct B'
autoptr.h:9:17: note: neither the destructor nor the class-specific operator
delete will be called, even if they are declared when the class is defined.
The only error message that is relevant is "invalid use of incomplete type 'struct B'". Basically it means that main.cpp now needs to include b.h, but why? Because the auto-generated constructor is inlined when we instantiate a
in main.cpp. Okay, but does this always have to happen or does it depends on the compiler? The answer is that it can't depend on the compiler. No compiler can make an auto-generated constructor non-inline. The reason for that is that it doesn't know where to put its code. From the programmer's point of view the answer is obvious: the constructor should go in the unit where all other methods of the class are defined, but the compiler doesn't know which unit is that. And besides, class methods could be spread across several units and sometimes it even makes sense (like if a part of the class is auto-generated by some tool).
And of course, if we make A::A()
explicitly inline either by using the inline keyword or by putting its definition inside the A class declaration, the same compilation error would occur, possibly a bit less cryptic.
The conclusion
It seems it's perfectly fine to employ the technique described above for auto-instantiated pointers. The only thing I'm not sure of is that AutoPtr<B> b;
thing inside a.h will work with any compiler. I mean, we can use a forward-delcared class when declaring pointers and references, but is it always correct to use it as a template instantiation parameter? I think there's nothing wrong with that, but compilers may think otherwise. Googling didn't yield any useful results on it either.