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