Should one use forward declarations instead of inc

2019-01-03 12:11发布

Whenever a class declaration uses another class only as pointers, does it make sense to use a class forward declaration instead of including the headerfile in order to pre-emptively avoid problems with circular dependencies? so, instead of having:

//file C.h
#include "A.h"
#include "B.h"

class C{
    A* a;
    B b;
    ...
};

do this instead:

//file C.h
#include "B.h"

class A;

class C{
    A* a;
    B b;
    ...
};


//file C.cpp
#include "C.h"
#include "A.h"
...

Is there any reason why not to do this wherever possible?

9条回答
We Are One
2楼-- · 2019-01-03 12:56

Is there any reason why not to do this wherever possible?

Convenience.

If you know ahead of phase that any user of this header file will necessarily need to also include the definition of A to do anything (or perhaps most of the times). Then it is convenient to just include it once and for all.

This is a rather touchy subject, as a too liberal use of this rule of thumbs will yield a nigh uncompilable code. Note that Boost approaches the problem differently by providing specific "convenience" headers which bundles a couple of close functionalities together.

查看更多
地球回转人心会变
3楼-- · 2019-01-03 12:56

Should one use forward declarations instead of includes wherever possible?

No, explicit forward declarations should not be considered as a general guideline. Forward declarations are essentially copy and pasted, or misspelled code, which in case you find a bug in it, need to fixed everywhere the forward declarations are used. This can be error-prone.

To avoid mismatches between the "forward" declarations and its definitions, put declarations in a header file and include that header file in both the defining and the declaration-using source files.

In this special case, however, where only an opaque class is forward declared, this forward declaration may be okay to use, but in general, to "use forward declarations instead of includes whenever possible", like the title of this thread says, can be quite risky.

Here are some examples of "invisible risks" concerning forward declarations (invisible risks = declaration mismatches that are not detected by the compiler or linker):

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

  • Explicit forward declarations of symbols representing functions may also be unsafe, like the parameter types and the number of parameters.

The example below illustrates this, e.g., two dangerous forward declarations of data as well as of a function:

File a.c:

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

File b.c:

#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 the program with g++ 4.7.1:

> g++ -Wall -pedantic -ansi a.c b.c

Note: Invisible danger, since g++ gives no compiler or linker errors/warnings
Note: Omitting extern "C" leads to a linking error for function() due to the c++ name mangling.

Running the program:

> ./a.out
truncated=abcd, forgotten="♀♥♂☺☻"
accessing data[1270][1023]
Segmentation fault
查看更多
戒情不戒烟
4楼-- · 2019-01-03 12:58

Is there any reason why not to do this wherever possible?

Yes - Performance. Class objects are stored with their data members together in memory. When you use pointers, the memory to the actual object pointed to is stored elsewhere on the heap, usually far away. This means accessing that object will cause a cache miss and reload. This can make a big difference in situations where performance is crucial.

On my PC the Faster() function runs approx 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();
    }
}

In parts of applications which are performance-critical or when working on hardware which is especially prone to cache coherence problems, data layout and usage can make a huge difference.

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

查看更多
登录 后发表回答