问题
First, the big picture. I have a Logger class. I've created a simplified interface for the class, and created a library for the interface. I'd like to use pimpl to hide the Logger class implementation, so the user doesn't need the Logger's headers. I'm having a bad time with template functions...
The Logger header is defined like this
/* Logger.h */
class Logger
{
public:
virtual ~Logger(){};
public:
template <typename... Args> void log(const char* fmt, const Args&... args)
{
printf(fmt, &args...);
}
};
std::shared_ptr<Logger> create_logger()
{
return std::shared_ptr<Logger>(new Logger());
}
FIRST VERSION
I've created the interface like this
/* LoggerInterface.h */
#include "Logger.h"
class LoggerInterface
{
public:
LoggerInterface();
public:
template <typename... Args> void log(const char* fmt, Args&&... args)
{
logger->log(fmt, std::forward<Args>(args)...);
}
private:
std::shared_ptr<Logger> logger;
};
/* LoggerInterface.cpp */
#include "LoggerInterface.h"
LoggerInterface::LoggerInterface()
{
logger = create_logger();
}
I've generated the library, and here is a sample of a main.cpp that uses it
/* main.cpp */
#include <LoggerInterface.h>
int main()
{
LoggerInterface loggerIntreface;
loggerIntreface.log("Welcome %s\n", "logger");
return 0;
}
Everything works, BUT the main includes LoggerInterface.h
, so implicitly includes Logger.h
. I'd like to get rid of Logger.h
on the user code side.
HIDE LOGGER ATTEMPT
I tried to use the pimpl idiom, but the template function is giving me headaches. I've read several docs and done many tests, but no luck so far. Here is the code of the "almost-there" version.
/* LoggerInterface.h */
class LoggerInterface
{
private:
class LoggerImpl;
public:
LoggerInterface();
public:
template <typename... Args> void log(const char* fmt, Args&&... args);
private:
std::shared_ptr<LoggerImpl> logger;
};
/* LoggerInterfacePrivate.h */
#include "Logger.h"
#include "LoggerInterface.h"
class LoggerImpl : public Logger
{}
template <typename... Args> inline void LoggerInterface::log(const char* fmt, Args&&... args)
{
logger->log(fmt, std::forward<Args>(args)...);
}
/* LoggerInterface.cpp */
#include "LoggerInterfacePrivate.h>
LoggerInterface::LoggerInterface()
{
logger = std::dynamic_pointer_cast<Logger>(create_logger());
}
Nothing new on the main side
/* main.cpp */
#include <LoggerInterface.h>
int main()
{
LoggerInterface loggerIntreface;
loggerIntreface.log("Welcome %s\n", "logger");
return 0;
}
The main includes LoggerInterface.h
, but doesn't need Logger.h
due to the pimpl. Compiles ok, but awfully I get an unresolved external symbol error for the template log()
function.
Any idea about how to get rid of the error? Is my approach a good one, or is it better to follow a different implementation to achieve my goal (create a library interface that the user can use without the base Logger class headers)?
IMPORTANT NOTE: I can't edit the Logger class.
回答1:
Simple answer is - you can't. The problem is that the interface method is instantiated as a "different function" with every different set of parameters, and in turn creates a different instantiation of the Logger function template each time. In order to do that, it needs to have the entire definition of the Logger method accessible to be able to generate the new instantiation.
If you hide it, you will indeed get the unresolved externals for all the missing instantiations, because they are not created when the LoggerInterface.cpp is build (as it is not known at that time which all instantiations will be needed).
If there would be only some limited set of possible instantiations, you could explicitly instantiate all the needed versions and they could be exported from a library/used via PIMPL. However as this is not the case here (the types and count of arguments to the log() method can be quite arbitrary), this solution would not be practical in this case.
来源:https://stackoverflow.com/questions/45239107/hide-template-function-declaration-in-library