34

If I create a class like so:

// B.h
#ifndef _B_H_
#define _B_H_

class B
{
private:
    int x;
    int y;
};

#endif // _B_H_

and use it like this:

// main.cpp
#include <iostream>
#include <vector>

class B; // Forward declaration.

class A
{
public:
    A() {
        std::cout << v.size() << std::endl;
    }

private:
    std::vector<B> v;
};

int main()
{
    A a;
}

The compiler fails when compiling main.cpp. Now the solution I know is to #include "B.h", but I'm curious as to why it fails. Neither g++ or cl's error messages were very enlightening in this matter.

Orion Edwards
  • 121,657
  • 64
  • 239
  • 328
Bernard
  • 45,296
  • 18
  • 54
  • 69
  • Note you _can_ pass a `vector` into a function with only forward declared type `T` if you pass it as a `vector&` (but not `vector` because that would require a copy operation) – bobobobo Jul 26 '13 at 18:01
  • "The compiler fails"... What was the error? At which line? – Antonio May 19 '21 at 08:03
  • 1
    A good request for clarification, @Antonio, but as I posted this question nearly thirteen years ago, I'm afraid the details escape me. – Bernard May 19 '21 at 20:51
  • My guess is that the error was at the `v.size()` line. Honestly, as it stands now this question would be better deleted. This gives a much better perspective on the problem https://stackoverflow.com/questions/38898935/forward-declaration-of-objects-with-stl-containers – Antonio May 20 '21 at 13:16
  • Does this answer your question? [Forward declaration of objects with STL containers](https://stackoverflow.com/questions/38898935/forward-declaration-of-objects-with-stl-containers) – Antonio May 20 '21 at 13:17
  • 1
    The question was answered to my liking years and years ago. – Bernard May 20 '21 at 22:33

8 Answers8

35

In fact your example would build if A's constructor were implemented in a compile unit that knows the type of B.

An std::vector instance has a fixed size, no matter what T is, since it contains, as others said before, only a pointer to T. But the vector's constructor depends on the concrete type. Your example doesn't compile because A() tries to call the vector's ctor, which can't be generated without knowing B. Here's what would work:

A's declaration:

// A.h
#include <vector>

class B; // Forward declaration.

class A
{
public:
    A(); // only declare, don't implement here

private:
    std::vector<B> v;
};

A's implementation:

// A.cpp
#include "A.h"
#include "B.h"

A::A() // this implicitly calls vector<B>'s constructor
{
    std::cout << v.size() << std::endl;
}

Now a user of A needs to know only A, not B:

// main.cpp
#include "A.h"

int main()
{
    A a; // compiles OK
}
fyzix
  • 351
  • 3
  • 3
  • 2
    Note that this works on GCC and Clang, but not Visual Studio. VS2015 fails with the error `error C2036: 'B *': unknown size` The reason is not entirely clear, but it may be that VS2015 is doing earlier template instantiation of `vector` than GCC/Clang. – Ky Waegel Sep 23 '16 at 22:54
  • 2
    Just to add mor information to the matter... Visual Studio VS2017 now works like `gcc` and compiles just fine. – Hopobcn Jul 30 '18 at 09:25
32

The compiler needs to know how big "B" is before it can generate the appropriate layout information. If instead, you said std::vector<B*>, then the compiler wouldn't need to know how big B is because it knows how big a pointer is.

Curt Hagenlocher
  • 20,680
  • 8
  • 60
  • 50
  • 1
    But as I know, std::vector v should only contain a pointer to B*, which should be of the same effect as defining: B* here – Baiyan Huang Mar 25 '11 at 13:42
  • It's not just a pointer to B -- at a minimum, it needs to create an array of B to store into that pointer. And to create the array, you need to know the size of B. – Curt Hagenlocher Apr 25 '11 at 01:52
  • 23
    @lzprgmr: Indeed, `vector` probably only contains a pointer to T, so I disagree with Curt's answer. The layout of `vector` can be known without knowing the definition of `B`, but since `vector` is a template, all its member functions must be instantiated for each template argument: you can't separate their declarations from their implementations. Since some of these member functions will need `T`'s definition, it must be a complete type to be used in a `vector`. The issue is due to member functions being defined inline, not to `vector`'s layout depending on `B`'s layout. – Luc Touraille Nov 18 '11 at 14:30
  • 4
    @LucTouraille: Wait, isn't this answer completely wrong? As other people say, the problem here is the inline constructor, not the container per se... – Nemo May 22 '15 at 16:22
  • 1
    @Nemo: Yes, I think it is incorrect. It is perfectly possible to implement a container in a way that does not require a complete type, in fact many Boost containers can be instantiated with an incomplete type without trouble. The containers of the standard library require a complete type because the standard says so, and the standard says so mainly for historical reasons, as explained [here](http://stackoverflow.com/a/18672346/20984). – Luc Touraille May 23 '15 at 14:13
6

To instantiate A::v, the compiler needs to know the concrete type of B.

If you're trying to minimize the amount of #included baggage to improve compile times, there are two things you can do, which are really variations of each other:

  1. Use a pointer to B
  2. Use a lightweight proxy to B
Josh
  • 7,936
  • 5
  • 41
  • 63
3

It's more than just the size of B that's needed. Modern compilers will have fancy tricks to speed up vector copies using memcpy where possible, for instance. This is commonly achieved by partially specializing on the POD-ness of the element type. You can't tell if B is a POD from a forward declaration.

MSalters
  • 173,980
  • 10
  • 155
  • 350
3

Just like fyzix said, the reason your forward declaration is not working is because of your inline constructor. Even an empty constructor might contain lots of code, like the construction of non-POD members. In your case, you have a vector to initialize, which you can't do without defining its template type completely.

The same goes for destructors. The vector needs the template type definition to tell what destructor to call when destroying the instances it holds.

To get rid of this problem, just don't inline constructors and destructors. Define them separately somewhere after B is completely defined.

For more information, http://www.chromium.org/developers/coding-style/cpp-dos-and-donts

Raslanove
  • 649
  • 9
  • 16
1

This doesn't matter whether you use a vector or just try to instantiate one B. Instantiation requires the full definition of an object.

Greg Rogers
  • 35,641
  • 17
  • 67
  • 94
0

Man, you're instancing std::vector with an incomplete type. Don't touch the forward declaration, just move the constructor's definition to the .cpp file.

daotheman
  • 19
  • 1
-13

The reason you can't use a forward declaration is because the size of B is unknown.

There's no reason in your example that you can't include B.h inside of A.h, so what problem are you really trying to solve?

Edit: There's another way to solve this problem, too: stop using C/C++! It's so 1970s... ;)

Brad Wilson
  • 67,914
  • 9
  • 74
  • 83
  • 3
    'stop using C/C++' doesn't appear to be a solution to the original problem. patient: "doctor, it hurts when i do this", doctor: "well, then don't do that!" Is there another language or tool that can interoperate well with C++ that you would suggest instead? – Aaron Sep 17 '08 at 17:31