Я создаю HAL для встроенной системы, и часть этого воссоздает printf
функциональность (через класс под названием Printer
). Поскольку это встроенная система, пространство кода имеет решающее значение, и я хотел бы исключить поддержку с плавающей точкой в printf
по умолчанию, но разрешить пользователю моего HAL включать его на индивидуальной основе без перекомпиляции моей библиотеки.
Все мои классы имеют свои встроенные определения методов в заголовочном файле.
printer.h
выглядит примерно так ….
class Printer {
public:
Printer (const PrintCapable *printCapable)
: m_printCapable(printCapable) {}
void put_char (const char c) { ... }
#ifdef ENABLE_PRINT_FLOAT
void put_float (const float f) { ... }
#endif
void printf (const char fmt[], ...) {
// Stuffs...
#ifdef ENABLE_PRINT_FLOAT
// Handle floating point support
#endif
}
private:
const PrintCapable *m_printCapable;
}
// Make it very easy for the user of this library to print by defining an instance for them
extern Printer out;
Теперь я понимаю, что это должно работать отлично.
printer.cpp
это красиво и просто:
#include <printer.h>
#include <uart/simplexuart.h>
const SimplexUART _g_simplexUart;
const Printer out(&_g_simplexUart);
Ненужный код раздувается:
Если я скомпилирую свою библиотеку с и проектирую без ENABLE_PRINT_FLOAT
определяется, то размер кода составляет 9 216 кБ.
Нужный код наворотов:
Если я скомпилирую библиотеку и проект с ENABLE_PRINT_FLOAT
, размер кода составляет 9,348 кБ.
Нужный код бло …. ой, подождите, это не раздутый
Если я скомпилирую проект с библиотекой без ENABLE_PRINT_FLOAT
, Я мог бы ожидать чтобы увидеть то же, что и выше. Но нет … вместо этого у меня есть размер кода 7,092 КБ и программа, которая не выполняется правильно.
Минимальный размер:
Если я компилирую оба компилируются без ENABLE_PRINT_FLOAT
тогда размер кода составляет всего 6 960 кБ.
Как я могу достичь своей цели небольшого размера кода, гибких классов и простого в использовании?
Система сборки CMake. Полный источник проекта Вот.
Основной файл это красиво и просто:
#include <printer.h>
void main () {
int i = 0;
while (1) {
out.printf("Hello world! %u %05.2f\n", i, i / 10.0);
++i;
delay(250); // 1/4 second delay
}
}
Если у вас есть другое определение inline
функции в разных единицах перевода у вас есть неопределенное поведение. Так как ваш printf()
определение меняется с настройкой ENABLE_PRINT_FLOAT
макрос вы просто видите этот эффект.
Обычно компилятор не выполняет встроенные функции, если считает их слишком сложными. Это создаст внеплановые реализации и выберет случайную при связывании. Так как случайным образом выбрать случайный порядок — это нормально … подождите, они разные, и программа может быть повреждена.
Вы мог сделайте поддержку плавающей запятой параметром шаблона вашего printf()
функция: функция будет вызвана с помощью
out.printf<false>("%d\n", i);
out.printf<true>("%f", f);
Реализация printf()
делегировал бы подходящим внутренним функциям (чтобы определения слияния компилятора были идентичными) с отключенной поддержкой плавающей запятой для false
случай: он может ничего не делать, провалиться или утверждать.
Может быть проще не делать никакой условной поддержки в первую очередь, а использовать потоковый интерфейс: поскольку функции форматирования для разных типов разделены, выбираются только те, которые фактически используются.
Если для вашей библиотеки есть возможность использовать C ++ 11, вы можете использовать шаблон с переменным числом аргументов, чтобы справиться с ситуацией: отдельный форматер будет реализован как отдельные функции, которые отправляются внутрь printf()
: этого пути нет printf()
функция, которая должна обрабатывать все форматирование. Вместо этого будут извлечены только необходимые средства форматирования типов. Реализация может выглядеть примерно так:
inline char const* format(char const* fmt, int value) {
// find format specifier and format value accordingly
// then adjust fmt to point right after the processed format specifier
return fmt;
}
inline char const* format(char const* fmt, double value) {
// like the other but different
}
// othe formatters
inline int printf(char const* fmt) { return 0; }
template <typename A, typename... T>
inline int printf(char const* fmt, A&& arg, T&& args) {
fmt = format(fmt, std::forward<A>(arg));
return 1 + printf(fmt, std::forward<T>(args));
)
Ясно, что существуют разные подходы к тому, как можно разделить общий код между разными форматерами. Тем не менее, общая идея должна работать. В идеале общий код должен выполнять как можно меньше работы, чтобы компилятор объединял весь нетривиальный код между различными видами использования. В качестве приятного побочного эффекта эта реализация может убедиться, что спецификаторы формата соответствуют передаваемым объектам и либо выдают подходящую ошибку, либо соответствующим образом обрабатывают формат каким-либо образом.