Xenoprimate
Xenoprimate

Reputation: 7963

C4150: Deletion of pointer to incomplete type and PIMPL idiom

is there any way to properly implement the PIMPL idiom with a template class in C++?

For example, I have the following PIMPL class:

template <typename T> struct PrivateImplementation {
    PrivateImplementation<T>(T* impl) : implementation(impl) {

    }
    ~PrivateImplementation<T>() {
        if (implementation != nullptr) delete implementation;
    }
    PrivateImplementation<T>(const PrivateImplementation<T>& c) = delete;
    void operator=(const PrivateImplementation<T>& a) = delete;
    PrivateImplementation<T>(PrivateImplementation<T>&& m) { 
        implementation = m.implementation;
        m.implementation = nullptr;
    }

    T* operator->() const {
        return implementation;
    }

    private:
    T* implementation;
};

However, when I use it like below, the compiler complains (a warning) that I'm deleting an incomplete type:

Logger.h

class LoggerStream {
    public:
    LoggerStream(std::wstring componentName);
    ~LoggerStream();
    LoggerStream(const LoggerStream&) = delete;
    void operator=(const LoggerStream&) = delete;
    LoggerStream(LoggerStream&&);

    void Write(std::wstring message, const char* __function__, const char* __file__, int __line__) const;
    void Write(std::wstring message) const;

    private:
    struct LoggerStreamImpl;
    PrivateImplementation<LoggerStreamImpl> impl;
};

Logger.cpp

struct LoggerStream::LoggerStreamImpl {
    LoggerStreamImpl(std::wstring componentName) : componentName(componentName) { }
    ~LoggerStreamImpl() = default;
    LoggerStreamImpl(const LoggerStreamImpl&) = delete;
    void operator=(const LoggerStreamImpl&) = delete;
    LoggerStreamImpl(LoggerStreamImpl&&);

    const std::wstring componentName;
};

LoggerStream::LoggerStream(std::wstring componentName)
    : impl(new LoggerStreamImpl { componentName }) { }

LoggerStream::~LoggerStream() { }

void LoggerStream::Write(std::wstring message, const char* __function__, const char* __file__, int __line__) const {
    // Some implementation
}

void LoggerStream::Write(std::wstring message) const {
    // Some implementation
}

I clearly have a defined destructor for LoggerStreamImpl right there in the .cpp, so what gives?

Thank you.

Upvotes: 0

Views: 949

Answers (2)

Darinth
Darinth

Reputation: 510

Any file that includes logger.h attempts to compile the template for itself. So, lets say you have a main.cpp file that contains your int main() (or other entry point) and that file includes logger.h. Main.cpp will see logger.h, try to parse it, and during the process of parsing it it will try to compile a version of the PrivateImplementation template for T = LoggerStreamImpl. You may be able to get away with this if your compiler is C++11 compliant and allows you to tell it that PrivateImplementation is defined externally.

Upvotes: 0

galop1n
galop1n

Reputation: 8824

When i store a std::unique_ptr with a type i do not want fully visible, like pimpl, i use a custom deleter where the operator() is defined in the cpp with the full class visible.

It do the trick, and with Link Time Optimisation, even the function call indirection introduced can be optimized if the compiler think it is pertinent.

Upvotes: 1

Related Questions