Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C++ Use Of Undefined Type

Tags:

c++

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();
like image 245
Xenoprimate Avatar asked Jan 25 '14 23:01

Xenoprimate


2 Answers

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.

like image 160
derpface Avatar answered Oct 02 '22 15:10

derpface


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.

like image 32
Kerrek SB Avatar answered Oct 02 '22 14:10

Kerrek SB