0

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.

Xenoprimate
  • 7,691
  • 15
  • 58
  • 95

2 Answers2

1

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.

galop1n
  • 8,573
  • 22
  • 36
0

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.

Darinth
  • 511
  • 3
  • 14
  • I am using a C++11 compiler (but compliance is not exactly its strong point)... How would I tell it that 'PrivateImplementation is defined externally'? – Xenoprimate Jan 27 '14 at 21:29
  • The line `extern template class PrivateImplementation;` in logger.h just before the class definition should tell the compiler that this class is instantiated elsewhere and to not instantiate it. I believe you'll need to include the line `template class PrivateImplementation;` in logger.cpp to force that file to actually instantiate the template or it won't get instantiated anywhere. Let me know if this works as I've not played around with extern templates yet. – Darinth Jan 27 '14 at 21:37
  • No dice, adding `extern template class PrivateImplementation;` to **Logger.h** gives a compiler error:'extern template' cannot follow explicit instantiation of class "ophRuntime::PrivateImplementation"... And yes, I put it above the class declaration. :( – Xenoprimate Jan 27 '14 at 21:43
  • Did you put `template class PrivateImplementation;` somewhere in your code? If so, where? – Darinth Jan 27 '14 at 21:49
  • Above the first mention of `LoggerStreamImpl` in **Logger.cpp**. – Xenoprimate Jan 27 '14 at 21:54
  • If it's above the include for logger.h, try moving it below the include. If that doesn't work, than I'm not sure what to do. For testing purposes you could try adding `extern template class PrivateImplementation;` to all of the files that include logger.h except logger.cpp, but this seems like a lot of work and like there should be a better method of dealing with the problem. – Darinth Jan 27 '14 at 21:57
  • 1
    Yes, it was below the include. Thank you for your help, but eventually for now I will just do this the 'old-fashioned' way, with `delete` in the dtor. There's a point (especially with C++) that one has to just give up and submit :D – Xenoprimate Jan 27 '14 at 22:01