Reputation: 7963
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
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
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