Auto-generated smart pointer

I am looking for an easy way to reduce the coincidence of headers in a C ++ project, which is mainly due to the (cool) class, which of course requires a full type. For instance:

// header A class A { B b; // requires header B }; 

I also looked at interfaces and pimpl, but both imply some template code that I don't want to write / maintain manually (or is there a way to do this automatically?).

So, I thought about replacing an element with a pointer and a forward like class B* pB; , but this requires processing to create and delete the object. Well, I could use smart pointers to delete (not auto_ptr , though, since it requires a full type when creating, so say something like shared_ptr<class B> pB; ), but what about creating the object now?

I could create an object in constructor A , for example pB = new B; , but this, again, is manual, and even worse, there may be several constructors ... Therefore, I am looking for a way to do this automatically, which will work as simple as changing B b; on autoobjptr<class B> pB; in A definition without having to work with creating pB .

I'm sure this is not a new idea, so maybe you could give me a link to a general solution or discussion?

UPDATE: To clarify, I am not trying to break the dependency between A and B , but I want to avoid including the B header if A one is part of it. In practice, B used when implementing A , so a typical solution would be to create an interface or pimpl for A , but at the moment I'm looking for something easier.

UPDATE2: I suddenly realized that a lazy pointer like the one suggested here will do the trick (it's too bad there is no standard implementation of this in say boost), combined with a virtual destructor (to allow an incomplete type). I still do not understand why there is no standard solution and I feel that I am reinventing the wheel ...

UPDATE3: Suddenly, Sergey Tachenov came up with a very simple solution (the accepted answer), although it took me half an hour to understand why it really works ... If you remove the A () constructor or define it inline in the header file, the magic will no longer work (correction error). I believe that when you define an explicit constructor without a built-in structure, the construction of elements (even implicit ones) is done inside the same compilation unit (A.cpp), where type B ends. On the other hand, if your constructor A is built-in, the creation of members must occur inside other compilation units and will not work, since B is incomplete here. Well, that’s logical, but now I’m curious - is this behavior defined by the C ++ standard?

UPDATE4: Hopefully the latest update. Pay attention to the accepted answer and comments for the discussion of the above question.

+4
source share
5 answers

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 // TESTPQ_B_H 

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> #include "ah" #include "bh" A::A() { printf("A::A()\n"); } A::~A() { printf("A::~A()\n"); } void A::doB() { b->doSomething(); } 

Ok, and finally, main.cpp, which uses A but does not include "bh":

 #include "ah" int main() { A a; a.doB(); } 

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) // magic number { printf("A::A()\n"); } 

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.

+3
source

I am sure that this can be implemented in the same way as unique_ptr . The difference would be that the allocated_unique_ptr constructor would allocated_unique_ptr object B by default.

Note that if you want the automatic construction of object B, it will be created using the default constructor.

+1
source

Well, you yourself gave the best solution, use pointers and new them in the constructor ... If there are several constructors, repeat this code there. You can create a base class that will do this for you, but that will only puzzle the implementation ...

Have you thought about a template in class B ? It may also solve your cross-dependencies of the header, but will likely increase compilation time ... This leads us to try to avoid these #include s. Did you measure compile time? Does it bother you? This is problem?

UPDATE : example for a template:

 // Ah template<class T> class A { public: A(): p_t( new T ) {} virtual ~A() { delete p_t } private: T* p_t; }; 

Again, this will most likely not increase compilation time ( Bh will need to be pulled in to create an instance of template A<B> ), it allows you to delete the header and source files.

0
source

You can write an automatic pimpl_ptr<T> , which will automatically update, delete and copy the files contained in it.

0
source

The problem with your approach is that although you can avoid including the header file for B in this way, it really does not reduce the dependencies.

The best way to reduce dependencies is to let B get the base class declared in a separate header file and use the pointer to that base class in A. You still need to manually create the correct descendant (B) in constructor A, of course.

It is also very possible that the relationship between A and B is real, and in this case you are not improving anything by artificially avoiding including the header file B.

0
source

Source: https://habr.com/ru/post/1332230/


All Articles