Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Prevent instantiation of template class with an incomplete type

I'm writing a library. Its layout looks something akin to this:

/////////
// A.h //
/////////

#include <vector>

class B;

class A
{
    std::vector<B> Bs;

public:
    ...
};

/////////
// B.h //
/////////

class B
{
    ...
}

///////////
// A.cpp //
///////////

#include "A.h"
#include "B.h"

// Implementation of A follows
...

///////////
// B.cpp //
///////////

#include "B.h"

// Implementation of B follows
...

/////////////
// MyLib.h //
/////////////

#include "A.h"

As you can see, the only type accessible from the outside is supposed to be A, that's why B is declared as an incomplete type in A.h. The library itself compiles fine, but when it comes to using it in a program, the compiler issues errors like: invalid use of incomplete type B when I try to create an object of type A. These errors point to the vector header, specifically, to the std::vector's destructor, which apparently needs to know the size of the type it holds to deallocate the internal storage properly. What I think is happening is that the compiler is trying to instantiate std::vector<B>::~vector in my program, which can't succeed for aforementioned reasons. However, there are symbols for std::vector<B>::~vector in the library's binary, so I could do without instantiating it in the program. I'm looking for a way to tell that to the compiler. I've already tried changing MyLib.h to something like this:

/////////////
// MyLib.h //
/////////////

#include "A.h"
extern template class std::vector<B>;

This unfortunately doesn't work, because extern applies only to the code generation stage of the compilation, and the compiler still reports errors while parsing. I thought that maybe the compiler is trying to instantiate std::vector<B>::~vector because A has an implicit destructor, so I tried implementing a destructor manually like this:

/////////
// A.h //
/////////

#include <vector>

class B;

class A
{
    std::vector<B> Bs;
    ~A();

public:
    ...
};

///////////
// A.cpp //
///////////

#include "A.h"
#include "B.h"

A::~A() {}

// Further implementation of A follows
...

This doesn't help either, the compiler is still trying to instantiate std::vector<B>::~vector, even though it shouldn't ever be invoked outside the library code now. Is there a different way to achieve what I want or would it be best to choose a different method of information hiding for B?


1 Answers

Don't try and prevent instantiations with undefined types explicitly, let the compiler do its job. If you try and prevent instantiations with undefined types manually you might risk violating ODR, for more look here if-else depends on whether T is a complete type

You can dump the values in the vector in unique_ptrs to add a layer of indirection. For more information about how unique_ptr works with incomplete types look here std::unique_ptr with an incomplete type won't compile For example

main.cpp

#include <iostream>
#include <vector>
#include <memory>

#include "something.hpp"

using std::cout;
using std::endl;

int main() {

    Something something;

    return 0;
}

something.hpp

#pragma once

#include <vector>
#include <memory>

class Incomplete;
class Something {
public:
    Something();
    ~Something();
    std::vector<std::unique_ptr<Incomplete>> incompletes;
};

something.cpp

#include "something.hpp"

class Incomplete {};

Something::Something() {}
Something::~Something() {}

Also consider reading this blog post http://www.gotw.ca/gotw/028.htm, it outlines an alternative to dynamic allocation if you are against that

like image 103
Curious Avatar answered Dec 02 '25 20:12

Curious



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!