3

Let's assume that we have a source file A.cpp where we forward declare a type ClassB, and then we keep using pointers to ClassB without ever #including file B.cpp (where ClassB is defined); And in B.cpp we forward declare ClassA and use a pointer to it without ever #including A.cpp (where ClassA is defined), then is the compiler fully happy with that? and will symbols resolution work just fine? In other words, do these two object files need not know about one another at all before link-time?

(I am assuming compiling C++ code on visual studio without any changes to the default compiler)

PS:

File A.cpp

class ClassB;

class ClassA
{
bool JustTakeAClassBPointAndDoNothingWithIt(ClassB* class_b_pointer)
{
if(class_b_pointer)
return true;
else
return false;
return false;
}
}

File B.cpp

class ClassA;

class ClassB
{
bool JustTakeAClassAPointAndDoNothingWithIt(ClassA* class_a_pointer)
{
if(class_a_pointer)
return true;
else
return false;
return false;
}
}
Physician
  • 483
  • 2
  • 7
  • 1
    This is too vague and general to have an authoritative answer. C++ is the most complicated general purpose programming language in use today. The only way to answer "will this work", when you don't know, is to try it. P.S. Barring the case of compiler bugs, whether your compiler is "visual studio without any changes to the default compiler", or not, is completely irrelevant. C++ is C++. – Sam Varshavchik Dec 16 '21 at 12:01
  • by "then we keep using pointers to ClassB" do you mean that you just pass around the pointer and do generic pointer stuff on it; or do you mean, access anything specific to ClassB via that pointer? The latter will only work if you do include B's definition file (I think) – codeling Dec 16 '21 at 12:03
  • @SamVarshavchik Are you sure you read thoroughly? I disagree with your characterization. Nonetheless, Thanks for your time. – Physician Dec 16 '21 at 12:04
  • @codeling yes exactly, only pointer general manipulation. – Physician Dec 16 '21 at 12:05
  • 1
    then I see no reason why you shouldn't get away with the forward declaration only. But hard to say without explicit code example ;) – codeling Dec 16 '21 at 12:06
  • 1
    very much related: https://stackoverflow.com/questions/44963201/when-does-an-incomplete-type-error-occur-in-c. Admittedly, I dont understand why answers say that it is hard to tell. It is actually rather simple: A forward declared type is incomplete, you cant do anything that requires a complete type and thats it – 463035818_is_not_an_ai Dec 16 '21 at 12:22
  • @463035818_is_not_a_number thanks indeed. (the question was considered vague before I edited it and added code example, it was vague for most viewers to assume that nothing other than incomplete type allowable manipulation is what I was asking for, in hindsight, I think they are correct). – Physician Dec 16 '21 at 12:29
  • Just passing pointers around rarely achieves much. It's not possible to call non-static member functions of classes (like `ClassA::JustTakeAClassBPointAndDoNothingWithIt()` or `ClassB::JustTakeAClassAPointAndDoNothingWithIt()`) from any source file that doesn't have visibility of the class definitions for `ClassA` and `ClassB` respectively. Calling those members (without undefined behaviour) also requires there to be at least one instance of those classes, and it is not necessary to create an instance of any class unless at least one source file has visibility of the class definition. – Peter Dec 16 '21 at 12:40
  • @Peter That's obvious. Actually the question was just a part of trying to understand forward declaration in the linker sense, as I clearly mentioned in the end of the first paragraph. I have an overall idea about declarations, and definitions from the typical linker's point of view, and it is difficult to see any mention of forward declarations when you just start reading about the basics of a linker design. – Physician Dec 16 '21 at 13:45

2 Answers2

2

It depends on what exactly you want to do with the pointer - you will only be able to do very limited things with pointers to forward-declared types. For example, an assigment, like this, is fine:

class A;

void myFunc(A* a1)
{
    A* a2 = nullptr; 
    A* a3 = a1;
}

But you will not even be able to increment/decrement them (because for that, the compiler has to know the size of the objects to "walk over). Neither will you be able to construct an object of this type, or access any of its methods (because for that, of course the compiler needs to know what the type looks like, or what methods it has). See the examples given in the answer by XapaJIaMnu.

One additional point about your question - you mention including "B.cpp". This suggests that either

  • you only have one file which includes both your declaration and your definition, which is bad practice (split them up into .h and .cpp files! and only include the .h file!)
  • or that you are actually including the file with definitions instead of the header file - which is never required, unless you're doing something wrong; e.g. templates can be tricky in this regard - they basically should be defined in the headers as well.

In order to give more details, you'll have to provide more information on what exactly you want to do with such forward-declared pointers.

See also

codeling
  • 11,056
  • 4
  • 42
  • 71
2

This questions is really too general to answer properly, but here's my 2 cents. In general, as long as you only refer to a class as a pointer to it, compilation will work. eg this example compiles fine:

class B;

int main() {
    B * tst;
    return 0;
}

However, as soon as you try to actually instantiate a the pointer, or access any of its methods, you need a full definition. Those examples will NOT work:

class B;

int main() {
    B * tst = new B(); // error: allocation of incomplete type 'B'
    return 0;
}

Or:

class B;

int main() {
    B * tst;
    tst->print(); // error: member access into incomplete type 'B'
    return 0;
}

tl;dr; As long as you don't actually interact with it, you can use an incomplete type. If you use any method or function, you need to declare them in advance (including constructors)

XapaJIaMnu
  • 1,408
  • 3
  • 12
  • 28