문제

Given the following, working code.

#include <iostream>

template<class Detail>
class AbstractLogger
{
public:
    static void log(const char* str) {
        Detail::log_detailled(str);
    }
};

class Logger : public AbstractLogger<Logger>
{
public:
    static void log_detailled(const char* str) {
        std::cerr << str << std::endl;
    }
};

int main(void)
{
    AbstractLogger<Logger>::log("main function running!");
    return 0;
}

Now, I want to put AbstractLogger into a library, and let the library user define his own logger, like the Logger class here. This has one drawback: AbstractLogger<Logger> can not be used inside the library, since the library can not know Logger.

Notes:

  • Please no virtual functions or questions why not. Also, I am aware of the similar problem that "static virtual" members are invalid. Maybe, there is a workaround in CRTP :)
  • C++11 will be interesting, however, I need "usual" C++.
도움이 되었습니까?

해결책

The usual approach is to code against a concept, while providing helpers so that users may easily produce types that satisfy one or more of those concepts. As an example, something like boost::iterator_facade is a CRTP helper that makes it easier for a user to write an iterator. Then, that iterator can be used anywhere an iterator is accepted -- for instance in the range constructor of std::vector. Notice how that particular constructor has no foreknowledge of the user-defined type.

In your case, AbstractLogger would be the CRTP helper. The missing piece would be to define e.g. a logger concept. As a result, notice that everything that needs a logger either needs to be implemented as a template or you need a type-erasing container to hold arbitrary loggers.

Concept checks (like those provided by Boost) are convenient for this kind of programming, since they allow to represent a concept with actual code.

다른 팁

If what you mean is that you want to have a library that uses this as a logging mechanism without knowing the exact instantiating type, I would advice against it.

The only way of doing it while meeting your other requirements (i.e. no virtual functions) is that all your functions/types in the library that need to log are converted into templates that take the Logger type. The net result is that most of your interface becomes a template (although you can probably move a good amount of the implementation to non-templated code, it will make your life much harder than needed, and it will still generate a much larger binary).

If your concern with virtual functions is performance, then you should reconsider your approach and the problems it brings. In particular, logging is expensive. Most logging libraries tackle it by optimizing the non-logging case (by means of macros that avoid calling the logger if the log level/group/... are not enabled), but still leave dynamic dispatch for the actual writting. The cost of the dynamic dispatch is negligible compared with the cost of writing to the console, or a file, or even with the cost of generating the message that will be logged (I am assuming that you not only log literal strings)

Template classes can't be 'put in a library' since they are instantiated by the compiler as specializations of their template parameters.

You may put parameter independent stuff used in the template implementation into a library though.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top