Учитывая следующее, рабочий код.
#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;
}
Теперь я хочу поставить AbstractLogger
в библиотеку, и пусть пользователь библиотеки определяет свой собственный регистратор, как Logger
класс здесь. Это имеет один недостаток: AbstractLogger<Logger>
нельзя использовать внутри библиотеки, так как библиотека не может знать Logger
,
Заметки:
Обычный подход состоит в том, чтобы кодировать концепцию, обеспечивая при этом помощников, чтобы пользователи могли легко создавать типы, которые удовлетворяют одному или нескольким из этих концепций. Как пример, что-то вроде boost::iterator_facade
является помощником CRTP, который облегчает пользователю написание итератора. Затем этот итератор может использоваться везде, где принят итератор — например, в конструкторе диапазона std::vector
, Обратите внимание, что этот конкретный конструктор не знает заранее определенного пользователем типа.
В твоем случае, AbstractLogger
будет помощником CRTP. Недостающий кусок будет определять, например, концепция логгера. В результате обратите внимание, что все, что требует регистратора, либо должно быть реализовано в виде шаблона, либо вам нужен контейнер для стирания типов для хранения произвольных регистраторов.
Проверка концепции (например, предоставленные Увеличение) удобны для этого вида программирования, поскольку они позволяют представлять концепцию с помощью реального кода.
Если вы имеете в виду, что вы хотите иметь библиотеку, которая использует это как механизм ведения журнала, не зная точного типа создания экземпляра, я бы посоветовал против этого.
Единственный способ сделать это при выполнении других ваших требований (то есть без виртуальных функций) состоит в том, что все ваши функции / типы в библиотеке, которые должны регистрироваться, преобразуются в шаблоны, которые принимают Logger
тип. Конечным результатом является то, что большая часть вашего интерфейса становится шаблоном (хотя вы, вероятно, сможете переместить большую часть реализации в не шаблонный код, это сделает вашу жизнь намного сложнее, чем необходимо, и все равно будет генерировать намного больший двоичный код) ,
Если вы заботитесь о виртуальных функциях, это о производительности, то вам следует пересмотреть свой подход и проблемы, которые он вызывает. В частности, ведение журнала является дорого. Большинство библиотек журналов решают эту проблему, оптимизируя случай отсутствия регистрации (с помощью макросов, которые избегают вызова регистратора, если уровень журнала / группа / … не включены), но все еще оставляют динамическую диспетчеризацию для фактической записи. Стоимость динамической отправки незначительна по сравнению со стоимостью записи в консоль или файл, или даже со стоимостью генерации сообщения, которое будет записано в журнал (я предполагаю, что вы записываете не только литеральные строки)
Шаблонные классы не могут быть «помещены в библиотеку», так как они создаются экземпляром компилятором как специализация их параметров шаблона.
Вы можете поместить независимые от параметров вещи, используемые в реализации шаблона, в библиотеку.