1

I looked around to find the rationale behind the choice made in C++ compiler about what I've called imaginary friends. Unfortunately I didn't find any explanation so I'm asking experts.

I wondering why C++ does not complain about the following code. Classes and method indicate as friends do not exist actually but if you compile the program it will compile and run.

#include <iostream>

class TrueFriend
{
    friend class NotExistingClass;
    friend class AnotherNotExistingClass;
    friend int NotExistingFunc();
  public:
     void TellAboutYou() { std::cout << "I have a lot of friends" << std::endl; }
};

int main(int argc, char **argv)
{
   TrueFriend instance;
   instance.TellAboutYou();
   return 0;
}
user2807083
  • 2,962
  • 4
  • 29
  • 37
MadBlack
  • 327
  • 4
  • 15
  • 6
    forward declaring a type and then not defining it, doesn't cause an error in the compilers I've used if you don't actually use the type. – PeterT Jan 27 '21 at 14:52
  • 1
    I don't feel the keyword `friend` is really relevant here, you can do the same without it [godbolt link](https://godbolt.org/z/c9z1EM). – KamilCuk Jan 27 '21 at 14:57
  • 1
    One way to think about it is to recall that C++ compiles a single translation unit at a time, without knowing the contents of other translation units. `friend class NotExistingClass;` is a forward declaration that essentially says "When you get around to compiling `NotExistingClass`, it's a `friend` of `TrueFriend`." But at the time you compile `TrueFriend` in one file, how is it supposed to know if you compile `NotExistingClass` _eventually_ in some other file or _never_? And the answer is it can't know and doesn't have to care, so it doesn't. – Nathan Pierson Jan 27 '21 at 15:06
  • These declaration are not so much "this exists, and is my friend" as they are "if this exists, it is my friend". (Declaring a type that you never define is surprisingly useful, by the way.) – molbdnilo Jan 27 '21 at 15:14
  • @NathanPierson that's correct but my example is an executable so it will pass the link phase as well. It just seem odd to me and it happens that a code like this is just cluttered with useless information makes it difficult to read the code. – MadBlack Jan 27 '21 at 15:15

2 Answers2

4

There is no reason to make this code fail to compile. The friend declaration is sufficient to declare the type. And in your code no definition is required. Maybe this would be more clear:

class NotExistingClass;
class AnotherNotExistingClass;
int NotExistingFunc();

class TrueFriend
{
     friend NotExistingClass;
     friend AnotherNotExistingClass;
     friend int NotExistingFunc();
   public:
     void TellAboutYou() { std::cout << "I have a lot of friends" << std::endl; }
};

with the same effect: The classes and the function are declared but no definition is available. Here you can read about the advantages of forward declaration and why they are needed sometimes: What are forward declarations in C++?

For example consider two types A and B that are mutual friends. Thats a scenario where you need a forward delcaration because the headers cannot mutually include each other:

// A.h
class B;
class A{ friend B;};

// B.h
#include "A.h"
class B{ friend A;};

And a shorter way to write A.h is

class A{ friend class B; };

Now consider you start a new project that uses only A but not B. You include A.h, never use B and all is fine.

PS: As mentioned in a comment, the friend is not really essentail for your question. Having types and functions declared but not defined is not that uncommon and sometimes cannot be avoided.

463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
2

I wondering why C++ does not complain about the following code.

C++ is a programming language specification.

You could read some C++ standard like n3337 (or a newer one). That specification does not mention "complaints".

GCC is a compiler implementing more or less C++. You could add your own GCC plugin emitting the diagnostic you want.

Such a GCC plugin would slow down the compilation.

You could try to use the Clang static analyzer. Since it is open source, you are allowed to improve it.

BTW, there is a good case when non-existing friend classes are needed.

You are designing a GUI library (like FOX). In some cases, some widgets are not implemented (e.G. removed by some #ifdef HAVE_FOO_FEATURE), but their class needs to be a friend of some existing class. Think for example of some sound related widget: on a PC without speakers, it makes no sense. Or think of some OpenGL related widget on hardware without support for OpenGL. Or of some colored widget on black&white screens....

Or your C++ code has been configured by GNU autoconf. In some cases, some classes would have been disabled.

Or you are writing a library, and have collectively decided with your team that a future C++ class would be named NotExistingClass and your colleague John in charge of implementing it is on holidays, assigned to more urgent work, or sick.

Likewise, it is reasonable to declare void some_missing_member_function() inside some class WorkInProgessClass { ... } and implement that WorkInProgessClass::some_missing_member_function a few weeks later, when you really need it.

public APIs should be more stable than code implementing them.

The rationale is that in a project developed by a team, you want to specify (in documents and in stable header files) the common API, and implement it later. A few months later, you could discover that some defined and documented function was not needed at all.

And once Isabel (a developer) in your team is using WorkInProgessClass::some_missing_member_function she will immediately implement it (otherwise, the linker would complain). Likewise, if she declares an object of a forwarded class NotExistingClass the linker would emit some error (probably: call/use to undefined NotExistingClass::NotExistingClass() constructor...)

I love compiling C++ projects (like RefPerSys) with GCC using g++ -Wall -Wextra -g, and I would be annoyed in getting the warning you are dreaming of.

Another very related example is missing virtual methods (declared as virtual void foo(void) =0; in some abstract superclass) with concrete subclasses loaded from plugins at runtime (on Linux, using dlopen(3)...). AFAIK, Qt and FLTK are doing so.

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547
  • 6
    But how does answer the question? Telling people to just read the standard (and stop asking questions?) isn't really helpful. – Lukas-T Jan 27 '21 at 14:57
  • C++ can't complains, compiler could. This technically correct, the best kind of correctness – user2807083 Jan 27 '21 at 14:59
  • is an incomplete type that's never completed even a C++ error or undefined behavior? I can't find the answer – PeterT Jan 27 '21 at 15:00
  • 1
    This is an answer that's gotten a lot better as you've edited it, I suspect the initial downvoter(s) haven't seen the changes. – Nathan Pierson Jan 27 '21 at 16:00