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, ifA::~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?
- We are move constructing a
std::unique_ptr<B>
to initializeb
. b
is a local variable which means that its destructor will be called at the end of the scope.B
must be a complete type when the destructor forstd::unique_ptr<B>
is instantiated.B
is an incomplete type so we cannot destroyb
.
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
C++ #Include <Atlbase.H> Is Not Found
Non-Class Rvalues Always Have Cv-Unqualified Types
Dereferencing an Invalid Pointer, Then Taking the Address of the Result
How to Use Std::Sort with a Vector of Structures and Compare Function
What Does {0} Mean When Initializing an Object
Common Array Length MACro for C
What Do C and Assembler Actually Compile To
Performance Issue for Vector::Size() in a Loop in C++
Why Is Std::Unordered_Map Slow, and How to Use It More Effectively to Alleviate That
How to Construct Std::Array Object with Initializer List
How to Declare a Vector of Atomic in C++
Tackling Class Imbalance: Scaling Contribution to Loss and Sgd
Turn Off Eclipse Errors (That Aren't Really Errors)
What Are the Issues with a Vector-Of-Vectors
Is Std::Vector Copying the Objects with a Push_Back