Preventing an Incomplete Type Template Class Instance

I am writing a library. Its location looks something like 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 that is accessible from the outside must be A

, so it is B

declared an incomplete type in A.h

. The library itself compiles just fine, but when it comes to using it in a program, the compiler throws errors like: invalid use of incomplete type B

when I try to create an object of type A

. These errors point to the header vector

, in particular the destructor std::vector

, which apparently needs to know the size of the type it uses in order to deallocate the internal storage correctly. I think what is happening is that the compiler is trying to instantiate std::vector<B>::~vector

in my program, which cannot be done for the above reasons. However, the library has symbols forstd::vector<B>::~vector

so I could do without instantiating in the program. I'm looking for a way to tell the compiler about this. I've already tried changing MyLib.h

to something like this:

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

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

      

This unfortunately does not work because it extern

only applies to the compilation code generation stage and the compiler still reports errors when parsing. I thought that maybe the compiler is trying to instantiate std::vector<B>::~vector

because it A

has an implicit destructor, so I tried to implement the 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
...

      

That doesn't help either, the compiler still tries to instantiate it std::vector<B>::~vector

, although it should never be called outside of the library code. Is there any other way to achieve what I want, or would it be better to choose a different information hiding method for B

?

+3


source to share


3 answers


Don't try to prevent undefined types from being instantiated explicitly, let the compiler do its job. If you try to prevent instantiation with undefined types manually, you may run into ODR violation, see if-else for more details , depends on whether T is a complete type

You can reset the values ​​in the vector to unique_ptr

to add a layer of indirection. For more information on how unique_ptr

incomplete types work, see here std :: unique_ptr with incomplete types will not 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 describes an alternative to dynamic placement if you are against it

+2


source


WARNING POSSIBLE DARK MAGIC: I'm not sure what (or has since) been allowed - by the standard - to define std::vector<T>

an incomplete type member T

if none of the vector member functions ( including constructors and destructor ) are referenced from the current translation unit. I think C ++ 17 allows this.

One feature that compiles with C ++ 11 (although it might be illegal, see above) is to use a member union

to avoid calls to both the constructor (s) and the destructor std::vector

member:

struct Hidden;

struct Public {
    union Defer {
        std::vector<Hidden> v;
        Defer();
        // add copy/move constructor if needed
        ~Defer();
    } d;
};

      



Now, in your implementation file, you can (and should) actually call the vector's constructor and destructor:

struct Hidden { /* whatever */ };
Public::Defer::Defer() { new (&v) std::vector<Hidden>(); }
Public::Defer::~Defer() { v.~vector<Hidden>(); }

      

Of course, using a member in d.v

some way will need a definitionHidden

. Thus, you should restrict this use to member functions (non inline

) Public

that you implement in files that have access to the full definition Hidden

.

+2


source


You need space for the vector, and all vectors in every implementation I know occupy the same space no matter what they store.

We can use this by claiming that we are right.

struct A {
  std::aligned_storage_t<sizeof(std::vector<int>), alignof(std::vector<int>)> v;
  A();
  ~A();
};

      

in A.cpp

#include<b.h>
static_assert(sizeof(std::vector<B>)==sizeof(std::vector<int>), "size mismatch");
static_assert(alignof(std::vector<B>)==alignof(std::vector<int>), "align mismatch");

std::vector<B>& get_v(A& a){ return *(std::vector<B>*)&a.v; }
std::vector<B> const& get_v(A const& a){ return *(std::vector<B> const*)&a.v; }

A::A(){
  ::new ((void*)&v) std::vector<B>();
  try{
    // rest of ctor
  }catch(...){
    get_v(*this).~std::vector<B>();
    throw;
  }
}
A::~A(){
  get_v(*this).~std::vector<B>();
}

      

Also manually write copy / move ctor / assign.

We could automate this, but it's difficult. we need the fact that we are indeed a vector B in order to generate code only in A.cpp, nowhere else.

+1


source







All Articles