Forward Declaration with Unique_Ptr

Forward declaration with unique_ptr?

It's explicitly legal. The rule is that the types used to instantiate
a template in the standard library must be complete, unless otherwise
specified. In the case of unique_ptr, §20.7.1/5 says “[...] The
template parameter T of unique_ptr may be an incomplete type.”

There are certain operations on the pointer which require a complete
type; in particular, when the object will actually be destructed (at
least with the default deleter). In your example, for example, if
A::~A() were inline, this might cause problems. (Note that if you
don't declare the destructor yourself, it will be inline. Which
partially defeats the purpose of using std::unique_ptr.)

unique_ptr and forward declaration: the proper way to code a factory function

When the compiler instantiates the destructor of std::unique_ptr<Foo>, the compiler must find Foo::~Foo() and call it. This means that Foo must be a complete type at the point where std::unique_ptr<Foo> is destroyed.

This code is fine:

struct Foo;

std::unique_ptr<Foo> create();

...as long as you don't need to call the destructor of std::unique_ptr<Foo>! For a factory function that returns an std::unique_ptr to a class, that class needs to be a complete type. This is how you would declare the factory:

#include "foo.hpp"

std::unique_ptr<Foo> create();

You seem to be implementing pimpl with an std::unique_ptr correctly. You must define A::~A() at the point where B is complete (which is in the cpp file). You must define A::A() in the same place because B must be complete if you want to allocate memory and call its constructor.

So this is fine:

// a.hpp

struct A {
A();
~A();

private:
struct B;
std::unique_ptr<B> b;
};

// a.cpp

struct A::B {
// ...
};

A::A()
: b{std::make_unique<B>()} {}

A::~A() = default;

Now let's consider this (we'll pretend that I didn't make b private):

int main() {
A a;
auto b = std::move(a.b);
}

What exactly is going on here?

  1. We are move constructing a std::unique_ptr<B> to initialize b.
  2. b is a local variable which means that its destructor will be called at the end of the scope.
  3. B must be a complete type when the destructor for std::unique_ptr<B> is instantiated.
  4. B is an incomplete type so we cannot destroy b.

Ok, so you cannot pass around an std::unique_ptr<B> if B is an incomplete type. This restriction makes sense. pimpl means "pointer to implementation". It doesn't make sense for external code to access the implementation of A so A::b should be private. If you must access A::b then this is not pimpl, this is something else.

If you really must access A::b while keeping the definition of B hidden then there are a few workarounds.

std::shared_ptr<B>. This deletes the object polymorphically so that B doesn't need to be a complete type when the destructor of std::shared_ptr<B> is instantiated. It's not quite as fast as std::unique_ptr<B> and I personally prefer to avoid std::shared_ptr unless absolutely necessary.

std::unique_ptr<B, void(*)(B *)>. Similar to the way that std::shared_ptr<B> deletes the object. A function pointer is passed on construction that is responsible for deleting. This has the overhead of carrying around a function pointer unnecessarily.

std::unique_ptr<B, DeleteB>. The fastest possible solution. However, it's probably a little bit annoying if you have more than a handful of pimpl (but not really pimpl) classes because you can't define a template. This is how you would do it:

// a.hpp

struct DeleteB {
void operator()(B *) const noexcept;
};

// a.cpp

void DeleteB::operator()(B *b) const noexcept {
delete b;
}

Defining a custom deleter is probably the best option but if I were you, I'd find a way to avoid needing to access implementation details from outside the class.

How to use unique_ptr with forward declared type?

One needs to declare the destructor of the enclosing class B, and set this to default in the source file, such that the unique_ptr does not forcibly inline its own default deleter.

class B
{
public:
~B();
private:
std::unique_ptr<A> a;
};

and in the source file

#include <A.h>
... do stuff with ( a )
B::~B() = default;

Should do the job.

Can't use std::unique_ptr T with T being a forward declaration

You also need to put A's constructor in C.cpp:

A.h

#include <memory>
#include <vector>

class B;

class A {
public:
A();
~A();

private:
std::unique_ptr<B> m_tilesets;
};

C.cpp

#include "A.h"

class B {

};

A::~A() {

}

A::A() {

}

See this answer. The constructor needs access to the complete type as well. This is so that it can call the deleter if an exception is thrown during construction.

Use of undefined type with unique_ptr to forward declared class and defaulted move constructor/assignment

Yes, you need to have access to the full definition of B from wherever you instantiate std::unique_ptr<B>::~unique_ptr, because it needs to call B's destructor.

In your case, that means that A::~A's definition must be moved to a separate A.cpp file, which includes B.h.

unique_ptr with forward declared incomplete type won't compile

As mentioned by CuriouslyRecurringThoughts and Jarod42, the issue was due to the assignment of nullptr to m_ageDetectImplPtr

The following code works

    class AgeDetectImpl;
class AgeDetect {
public:
AgeDetect(std::string token);
~AgeDetect();

std::string getAge(std::string imagepath);
std::string getAge(uint8_t* buffer, size_t rows, size_t cols);
std::string getAge(const cv::Mat& image);

private:
std::unique_ptr<AgeDetectImpl> m_ageDetectImplPtr;
};


Related Topics



Leave a reply



Submit