Should forward declarations be used, and not included where possible?

When a class declaration uses another class only as pointers, does it make sense to use a direct class declaration instead of including a header file to proactively avoid circular dependency problems? therefore instead of:

//file Ch #include "Ah" #include "Bh" class C{ A* a; B b; ... }; 

do it instead:

 //file Ch #include "Bh" class A; class C{ A* a; B b; ... }; //file C.cpp #include "Ch" #include "Ah" ... 

Is there a reason why not doing this when possible?

+56
c ++ forward-declaration
Mar 28 '12 at 11:19
source share
9 answers

The direct declaration method is almost always better. (I can’t think of a situation where it’s better to include a file where you can use the forward declaration, but I’m not going to say that it’s always better just in case).

There are no drawbacks to class forwarding, but I can think of some drawbacks to include headers without the need:

  • longer compilation time, since all translation units, including Ch , will also include Ah , although they may not need it.

  • possibly including other headers you don't need indirectly

  • contamination of a translation unit with characters you don't need

  • you may need to recompile the source files that include this header if it changes (@PeterWood)

+47
Mar 28 '12 at 11:21
source share

Yes, using advanced ads is always better.

Some of the benefits they provide are:

  • Reduced compilation time.
  • No namespace.
  • (In some cases) can reduce the size of the generated binaries.
  • Recompilation time can be significantly reduced.
  • Prevention of potential clash of preprocessor names.
  • Implementing IMMOM PIMPL by providing a means to hide the implementation from the interface.

However, Forward declaring a class makes this class incomplete, and this seriously limits what operations you can perform in an incomplete type.
You cannot perform any operations that the compiler needs to determine the layout of the class.

With an incomplete type, you can:

  • Declare an element as a pointer or reference to an incomplete type.
  • Declare functions or methods that accept / return incomplete types.
  • Define functions or methods that accept / return pointers / references to an incomplete type (but do not use its elements).

With an incomplete type, you cannot:

  • Use it as a base class.
  • Use it to declare a member.
  • Define functions or methods using this type.
+28
Mar 28 '12 at 11:21
source share

Is there a reason why not doing this when possible?

Convenience.

If you know in advance that any user of this header file must also include Definition A in order to do something (or perhaps most of the time). Then it’s convenient to just turn it on once and for all.

This is a pretty tangible subject, since too liberal use of this rule of thumb will produce an almost incompatible code. Note that Boost approaches the problem in different ways by providing special “convenient” headers that bring several close functions together.

+14
Mar 28 '12 at 12:28
source share

One case where you do not want to have advanced ads is when they are complex. This can happen if some of your classes have templates, as in the following example:

 // Forward declarations template <typename A> class Frobnicator; template <typename A, typename B, typename C = Frobnicator<A> > class Gibberer; // Alternative: more clear to the reader; more stable code #include "Gibberer.h" // Declare a function that does something with a pointer int do_stuff(Gibberer<int, float>*); 

Advanced declarations are similar to duplicating code: if the code tends to change a lot, you have to change it 2 or more times each time, and that’s not good.

+8
Mar 28 2018-12-12T00:
source share

Should I use forward ads and not include them where possible?

No, explicit forward declarations should not be construed as general guidance. Forward declarations are essentially a copy and paste or an error code, which, if an error is detected, should be recorded everywhere where forward declarations are used. This may be error prone.

To avoid inconsistencies between forward declarations and its definitions, place the declarations in the header file and include this header file in both the source definition files and the declaration.

However, in this special case, when only an opaque class is declared forward, this forward declaration may be in order, but in general “use declarations ahead, and not include when possible,” for example, the header of this thread says it can be quite risky.

Here are some examples of “invisible risks” in relation to forward declarations (invisible risks = declaration inconsistencies that are not detected by the compiler or linker):

  • Explicit forward declarations of symbols representing data may be unsafe, because such forward declarations may require the correct knowledge of the size (s) of the data type.

  • Explicit forward declarations of characters representing functions may also be unsafe, for example, parameter types and number of parameters.

The following example illustrates this, for example, two dangerous forward data declarations, as well as functions:

Ac file:

 #include <iostream> char data[128][1024]; extern "C" void function(short truncated, const char* forgotten) { std::cout << "truncated=" << std::hex << truncated << ", forgotten=\"" << forgotten << "\"\n"; } 

Bc file:

 #include <iostream> extern char data[1280][1024]; // 1st dimension one decade too large extern "C" void function(int tooLarge); // Wrong 1st type, omitted 2nd param int main() { function(0x1234abcd); // In worst case: - No crash! std::cout << "accessing data[1270][1023]\n"; return (int) data[1270][1023]; // In best case: - Boom !!!! } 

Compiling a program using g ++ 4.7.1:

 > g++ -Wall -pedantic -ansi ac bc 

Note. Invisible danger, since g ++ does not give errors / warnings to the compiler or linker
Note. Omitting extern "C" results in a binding error for function() due to a C ++ name change.

Launching the program:

 > ./a.out truncated=abcd, forgotten="♀♥♂☺☻" accessing data[1270][1023] Segmentation fault 
+7
Jun 23 '14 at 18:00
source share

A funny fact, in its C ++ styleleguide language , Google recommends using #include everywhere, but avoiding circular dependencies.

+5
Dec 30 '16 at 17:11
source share

Is there a reason why not doing this when possible?

Absolutely: it breaks encapsulation, requiring the user of the class or function to know and duplicate implementation details. If these implementation details change, the code that declares ahead may be broken, while the code that relies on the header will continue to work.

Forward function declaration:

  • requires to know that it is implemented as a function, and not an instance of a static functor object or (goes out!) macro,

  • requires duplication of default values ​​for default parameters,

  • requires knowing your actual name and namespace, as it can simply be a using declaration, which pulls it into another namespace, possibly under an alias and

  • may lose inline optimization.

If the consumption code depends on the header, then all these implementation details can be changed by the function provider without breaking your code.

Forward class declaration:

  • requires to know whether it is a derived class and the base class (es) from which it is derived,

  • requires to know that this is a class, and not just a typedef or a specific instance of a class template (or, knowing that it is a class template, and all template parameters and default values ​​are correct),

  • requires knowing the true name and namespace of the class, as it can be a using declaration, which pulls it into another namespace, possibly under an alias and

  • requires knowledge of the correct attributes (it may have special alignment requirements).

Again, a forward declaration breaks the encapsulation of these implementation details, making your code more fragile.

If you need to reduce header dependencies to speed up compilation time, ask the class / function / library provider to provide a special forward declaration header. The standard library does this with <iosfwd> . This model preserves the encapsulation of implementation details and gives the library developer the ability to modify these implementation details without breaking your code, while reducing the load on the compiler.

Another option is to use the pimpl idiom, which hides implementation details even better and speeds up compilation at the expense of low runtime costs.

+3
Dec 30 '16 at 17:48
source share

Is there a reason why not doing this when possible?

The only reason I think is to preserve some typing.

Without forward announcements, you can include the header file only once, but I do not recommend doing this on any fairly large projects due to flaws noted by other people.

+1
Mar 28 2018-12-12T00:
source share

Is there a reason why not doing this when possible?

Yes - performance. Class objects are stored along with data items in memory. When you use pointers, the memory for the actual object that it points to is stored elsewhere on the heap, usually far away. This means that access to this object will lead to a miss and a reload of the cache. This can go a long way in situations where productivity is critical.

On my PC, the Faster () function works about 2000x faster than the Slower () function:

 class SomeClass { public: void DoSomething() { val++; } private: int val; }; class UsesPointers { public: UsesPointers() {a = new SomeClass;} ~UsesPointers() {delete a; a = 0;} SomeClass * a; }; class NonPointers { public: SomeClass a; }; #define ARRAY_SIZE 100000 void Slower() { UsesPointers list[ARRAY_SIZE]; for (int i = 0; i < ARRAY_SIZE; i++) { list[i].a->DoSomething(); } } void Faster() { NonPointers list[ARRAY_SIZE]; for (int i = 0; i < ARRAY_SIZE; i++) { list[i].a.DoSomething(); } } 

code>

In those parts of applications that are critical to performance or when working with equipment that is particularly prone to cache coherence issues, the layout and use of data can be of great importance.

This is a good presentation on this subject and other performance indicators: http://research.scee.net/files/presentations/gcapaustralia09/Pitfalls_of_Object_Oriented_Programming_GCAP_09.pdf

0
Jun 27 '13 at 12:25
source share



All Articles