At first I was intrigued by this question, because it looked like something really complicated, and all the comments about templates, dependencies and inclusions made sense. But then I tried to implement this and found it surprisingly easy. So either I misunderstood the question, or the question has some special property of looking much more complicated than it really is. Anyway, here is my code.
This is the illustrious 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
It looks very simple, and I wondered if it really works, so I made a test case for it. Here is my bh:
#ifndef TESTPQ_B_H #define TESTPQ_B_H class B { public: B(); ~B(); void doSomething(); }; #endif
And b.cpp:
#include <stdio.h> #include "bh" B::B() { printf("B::B()\n"); } B::~B() { printf("B::~B()\n"); } void B::doSomething() { printf("B does something!\n"); }
Now for class A, which actually uses this. Here ah:
#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>
Ok, and finally, main.cpp, which uses A but does not include "bh":
Now it actually compiles without a single error or 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++ -oa ao bo main.o d:\alqualos\pr\testpq>a B::B() A::A() B does something! A::~A() B::~B()
Does this help solve your problem, or am I doing something completely different?
EDIT 1: standard or not?
Well, it seemed like it was right, but now it brings us to other interesting questions. Here is the result of our discussion in the comments below.
What happens in the above example? The ah file does not need the bh file because it actually does nothing with b , it just declares it and knows its size, because the pointer in the AutoPtr class always has the same size. The only parts of autoptr.h that need B to be defined are the constructor and destructor, but they are not used in ah, so ah does not need to include bh
But why does ah not use constructor B? Are B fields initialized whenever we instantiate A? If so, the compiler may try to embed this code on every instance of A, but then it will fail. In the above example, it looks like the call to B::B() is placed at the beginning of the compiled constructor A::A() in the a.cpp block, but does its standard require it?
At first it seems that nothing stops the compiler from infiniting the field initialization code whenever a moment is created, therefore A a; turns into this pseudocode (not real C ++, of course):
A a; ab->B(); aA();
Can such compilers exist in accordance with the standard? The answer is no, they could not, and the standard has nothing to do with it. When the compiler compiles the "main.cpp" block, it does not know what the A :: A () constructor does. It could be a call to some special constructor for b , so insert the default value before it makes b twice initialized by different constructors! And the compiler is not able to check it, because the "a.cpp" block, where A::A() defined, is compiled separately.
Well, now you might think that if the smart compiler wants to look at the definition of B and if there is no constructor other than the standard one, then it would not put the call B::B() into the constructor A::A() and built-in instead, when A::A() called. Well, this will not happen because the compiler has no way of guaranteeing that even if B has no other constructors right now, it will not have any in the future. Suppose we add this to bh in the definition of class B:
B(int b);
Then we put its definition in b.cpp and change a.cpp accordingly:
A::A(): b(17)
Now that we recompile a.cpp and b.cpp, it will work as expected, even if we do not recompile main.cpp. This is called binary compatibility, and the compiler should not break this. But if he introduced a call to B::B() , we get main.cpp, which calls two b constructors. But since adding constructors and non-virtual methods should never interrupt binary compatibility, any sensible compiler should not allow this.
The final reason that such compilers do not exist is because it really makes no sense. Even if member initialization is embeddable, it will simply increase the size of the code and give absolutely no increase in performance, since there will still be one method call for A::A() , so why not let this method do all the work in one place?
EDIT 2: Well, what about the built-in and automatically generated constructors of A?
Another question arises, what happens if we remove A:A() from ah 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 ah:4:0, from main.cpp:1: autoptr.h: In constructor 'AutoPtr<T>::AutoPtr() [with T = B]': ah:8:9: instantiated from here autoptr.h:8:16: error: invalid use of incomplete type 'struct B' ah:6:7: error: forward declaration of 'struct B' autoptr.h: In destructor 'AutoPtr<T>::~AutoPtr() [with T = B]': ah: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' ah: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 matters is “invalid use of the incomplete type“ struct B. ”This basically means that main.cpp now needs to include bh, but why? Because the auto-generated constructor is built-in when we instantiate a in main. cpp. Okay, but does it always have to happen or does it depend on the compiler? The answer is that it cannot depend on the compiler. No compiler can make the automatically generated constructor not built-in. The reason for this is that it does not know where to put From the point of view of the programmer, the answer is obvious: the constructor must go to the block where all the other methods of the class are defined, but the compiler does not know which unit it is. In addition, the methods of the class can be distributed over several units, and sometimes it even makes sense (for example, if part of a class is autogenerated by some tool).
And of course, if we make A::A() explicitly inline, either with the inline keyword or by defining it inside the class A declaration, the same compilation error will occur, perhaps a little less cryptic.
Conclusion
It seems perfectly fine to use the technique described above for auto-accreted pointers. The only thing I'm not sure is that AutoPtr<B> b; the thing inside ah will work with any compiler. I mean, we can use the forward-delcared class when declaring pointers and references, but is it always correct to use it as a parameter to an instance of a template? I think there is nothing wrong with that, but compilers may think differently. Googling also did not produce any useful results.