I have the following class:
class Logger {
public:
DEFAULT_CONSTRUCTOR(Logger);
DEFAULT_DESTRUCTOR(Logger);
NO_DEFAULT_COPY_AND_ASSIGN(Logger);
bool Init(std::wstring logFileLocation, std::wstring assemblyVersion);
void Shutdown();
void const LogMessage(std::wstring message);
private:
struct LoggerImpl;
std::unique_ptr<LoggerImpl> impl; // Error here
};
However, when attempting to compile, I get the following errors:
// Errors:
// Error 1 error C2027: use of undefined type 'ophRuntime::Logger::LoggerImpl'
// Error 2 error C2338: can't delete an incomplete type c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory
// Warning 3 warning C4150: deletion of pointer to incomplete type 'ophRuntime::Logger::LoggerImpl'; no destructor called c:\program files (x86)\microsoft visual studio 12.0\vc\include\memory
Logger::LoggerImpl
does have a definition in the corresponding .cpp file:
struct LoggerStream::LoggerStreamImpl {
LoggerStreamImpl(std::wstring componentName) : componentName(componentName) { }
const std::wstring componentName;
NO_DEFAULT_COPY_AND_ASSIGN(LoggerStreamImpl);
};
And finally, the definition for those macros:
#define NO_DEFAULT_COPY_AND_ASSIGN(TypeName) \
TypeName(const TypeName&) = delete; \
void operator=(const TypeName&) = delete; \
TypeName(TypeName&&) = default; \
#define DEFAULT_CONSTRUCTOR(TypeName) TypeName();
#define DEFAULT_DESTRUCTOR(TypeName) ~TypeName();
Declare your destructor in the header and define it in the source file.
in the .h
class Logger {
public:
Logger(); // default constructor
~Logger(); // default destructor
private:
struct LoggerImpl; // impl forward declaration
std::unique_ptr<LoggerImpl> impl; // impl pointer
};
in the .cpp
// Define the impl structure
struct Logger::LoggerImpl
{
};
// Define the default constructor
Logger::Logger() :impl(std::make_unique<LoggerImpl>())
{
}
// Define the default destructor
Logger::~Logger()
{
}
That's it.
In your case, you say LoggerImpl
is defined in the .cpp like this, but I don't see a definition for LoggerImpl
, just a definition for LoggerStreamImpl
.
A unique pointer's parameter type needs to be a complete type in the translation unit where you instantiate the unique pointer:
struct Foo;
std::unique_ptr<Foo> p; // Error
The problem is of course that the unique pointer's destructor cannot be defined without the complete type of Foo
. If you need some kind of pimpling, you can get around that by delaying the destructor into a separate translation unit:
template <typename T> struct Impl
{
Impl();
~Impl();
std::unique_ptr<T> impl;
};
struct Foo
{
struct FooImpl;
Impl<FooImpl> impl;
};
int main()
{
Foo f;
}
This translation unit compiles, because the destruction of Foo::impl
is left to the destructor Impl<FooImpl>::~Impl<FooImpl>
, which is declared (the template class Impl<FooImpl>
is complete), and it's only its definition that is not available in the present translation unit.
To stress the point of all this once again: The pimpl construction presented here allows you to place the definition of the class Foo::FooImpl
into a separate, private translation unit, which consumers of the class Foo
never get to see. The price of this decoupling is an extra dynamic allocation (in the constructor of Impl<T>
) and an extra indirection (in the definitions of the member functions of Foo
, not shown), as well as a lack of inlining of the destructor of Impl<T>::impl
.
For more details see Herb Sutter's excellent GotW #101.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With