Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incomplete type for std::vector

The GCC compiler complains (see below) when I try the following. class Face needs to be incomplete because it contains pointer to class Element which similarly contains pointer to class Face. In other words, there is a circular dependency among classes. How can I fix it?

error: invalid application of ‘sizeof’ to incomplete type ‘Face’

class Face; // needs to be incomplete

class Element
{
    std::vector < std::unique_ptr <Face> > face;
};

class Face
{
    std::vector < std::unique_ptr <Element> > elm;
};
like image 215
Shibli Avatar asked Jun 01 '14 20:06

Shibli


People also ask

Can vector have different data types C++?

C++ is a statically-typed language. A vector will hold an object of a single type, and only a single type.

Is std::vector continuous?

Yes, the elements of a std::vector are guaranteed to be contiguous.

Can vector contain different data types?

ORIGINAL ANSWER: The easiest way to store multiple types in the same vector is to make them subtypes of a parent class, wrapping your desired types in classes if they aren't classes already.

What is incomplete type error in C++?

An incomplete type is a type that describes an identifier but lacks information needed to determine the size of the identifier. An incomplete type can be: A structure type whose members you have not yet specified. A union type whose members you have not yet specified.


1 Answers

One way to fix this is to declare the destructors and constructors of Element and Face but not define them in the header. Then you need to define them in cpp files.

(More technical details can be found in the answers to my question there: Is std::unique_ptr<T> required to know the full definition of T?)

The source of the problem is that the destructor of unique_ptr needs to call delete (by default) therefore it needs to know the definition of the type (to have it's size). But if the destructor of Element and Face is generated automatically, then it will be inlined by default: the code using Element and Face instances will be forced to know the size of both types so that their destructors can call unique_ptr destructor which can call delete with the type associated to the pointer.

The solution I gave will make sure the construction and destruction of the unique_ptr are defiend in a separate cpp. They will not be inlined but they are still callable by the code using Element and Face. The destructor code of the unique_ptrs will be in the cpp where the destructors of Element and Face are defined, so in these cpp the definition of both will be needed.

To take your example:

//header
class Face; // needs to be incomplete

class Element
{
public:
    Element(); // don't define it here
    ~Element(); // don't define it here
private:
    std::vector < std::unique_ptr <Face> > face;
};

class Face
{
public:
    Face(); // don't define it here
    ~Face(); // don't define it here
private:
    std::vector < std::unique_ptr <Element> > elm;
};

// cpp 
#include "header"
// if you want the default impl (C++11)
Element::Element() = default; 
Element::~Element() = default; 

Face::Face() = default; 
Face::~Face() = default; 

In case they are in different header/cpp pair, it's still the same solution. However you have to do more forward declare and the cpp files defining the construction/destruction have to include all the necessary headers:

//element.h
class Face; // needs to be incomplete

class Element
{
public:
    Element(); // don't define it here
    ~Element(); // don't define it here
private:
    std::vector < std::unique_ptr <Face> > face;
};

////////////////////////////////////////////////////////////
// face.h
class Element; // needs to be incomplete

class Face
{
public:
    Face(); // don't define it here
    ~Face(); // don't define it here
private:
    std::vector < std::unique_ptr <Element> > elm;
};

////////////////////////////////////////////////////////////
// element.cpp 
#include "element.h"
#include "face.h" // necessary to allow the unique_ptr destructor to call delete

// if you want the default impl (C++11)
Element::Element() = default; 
Element::~Element() = default; 

////////////////////////////////////////////////////////////
// face.cpp 
#include "element.h" // necessary to allow the unique_ptr destructor to call delete
#include "face.h" 

// if you want the default impl (C++11)
Face::Face() = default; 
Face::~Face() = default; 
like image 171
Klaim Avatar answered Sep 17 '22 14:09

Klaim