Перейти к содержанию
    

шаблонный класс - двойная компиляция в разных *.cpp файлах

Hola!!! Есть библиотечка у меня. Написана очень давно. Написана в си стиле. Изоляция переменных и локальных функций в файле .с , наружу торчать только интерфейсные функции. Реализует функционал эмулируемой eeprom c шагом слота по странице и ротацией произвольного количества страниц. 
На днях прилетела задачка реализовать 2 разные области eeprom. Решил накидать класс, чтобы иметь компактный и красивый доступ к функционалу и данным. Сейчас работает так

Spoiler
//===================
struct myStruct{
	uint16 bar;
	uint16 foo;
};

EEprom<myStruct>    ee1;
EEprom<int>         ee2;
  
int main(){
  ee1.init(1019, 2);	//2 страницы
  ee2.init(1017, 5);	//5 страниц, начиная с 1017
  
  ee1.data.bar = 0x55;  //нужная структура инстанцируется внутри объекта
  ee1.commit();
  
  ee2.data = 0x33;      //здесь напрямую, без структуры
  ee2.commit();
}

 

В целом мне очень нравится, но для этой красоты пришлось весь класс делать шаблонным и размещать его в .h файле. Теперь, если я использую, например ee1 в другом модуле .cpp и объявляю его там как extern EEprom<myStruct> ee1; , то для этого файла компилируется еще одна портянка кода, как и для первого. Вижу это по увеличению размера бинарника на выходе. Как избежать дублирования? 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Но ведь шаблоны именно так и работают - для каждого типа генерируется свой код.
Если структуру сделаете в виде указателя на структуру, тогда эта структура будет "натягиваться" на любой участок памяти и разбивать его как раз по "ячейкам" структуры.

Я конешн тут чето не понял задумку. Но если речь идет о внешней еепромке, то чтение и запись через интерфейс - всё равно побайтная (особенно через I2C), поэтому этот функционал не должен зависеть от типа представления данных. Просто начальный адрес и кол-во байт для чтения/записи.
А когда прочитали из еепромки в SRAM, можете поверх этих байтов "натянуть" указатель на структуру нужного типа и данные предстанут в виде этой структуры.

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Я о другом. Проблема в том, что даже если у меня будет один экземпляр EEprom<int> ee; на всю программу. Если я пытаюсь его использовать в разных модулях .cpp , при компиляции в каждом модуле будет весь объем используемого кода. Например я создал ee в файле main.cpp. А в файле test.cpp объявил extern EEprom<int> ee; При компиляции, насколько я понимаю собирается две копии функционала для каждого .cpp файла. Я исхожу из увеличения размера прошивки при использовании методов класса в файле test.cpp.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Связывание по extern, в целом, не очень хорошая затея. Лучше использовать механизмы наследования.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

24 minutes ago, Sverchok said:

Может это ответ на ваш вопрос https://en.wikipedia.org/wiki/C%2B%2B11#Extern_template.

Очень похоже. Попробовал разместить в одном из файлов, где используется функционал - extern template class EEprom<int>; Проект собирается, но линковщик не может найти нужные функции потом. У меня сейчас шаблон в файле eeprom.h - в нем весь функционал. По идее нужно дать компилятору сигнал собирать нужные методы на основе этого кода, а в остальных файлах - extern template class EEprom<int>; Как сделать - не пойму.
 

 

13 minutes ago, EdgeAligned said:

Связывание по extern, в целом, не очень хорошая затея. Лучше использовать механизмы наследования.

Не пойму, зачем наследовать? Насколько понимаю это породит новый класс, который тут же и откомпилируется. У меня всё работает, только код дублируется. У меня этот еепром дергается из 5 или 6 разных файлов. Это он для каждого по килобайту соберет болванку... с помощью extern я просто говорю, что мол такой-то инстанс есть уже где-то, отдай линковщику это всё.
 

Ещё интересно. Если я не вызываю какие-то функции, опять по размеру прошивки, я вижу, что они не компилируются. При использовании этого подхода -  extern template class EEprom<int>; - можно как-то компилятор заставить компилировать только нужное, а не всю функциональность?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Легко и просто: при создании объектов классов ClassA и ClassB, которые наследуют один и тот же класс Eeprom, методы класса Eeprom будут включены один раз (при условии, что шаблонный тип TData один и тот же. Локальные поля класса Eeprom будут раздельно размещены в объектах классов ClassA и ClassB. Доступ к методам класса Eeprom может быть сделан как показано на скрине - из любого класса, являющегося наследником Eeprom, эти методы доступны.

Снимок экрана 2024-02-19 024205.jpg

Изменено пользователем EdgeAligned
добавил скрин

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Получилось собрать. И даже размер прошивки зависит от количества используемых функций. Но получается уже не так красиво, как хотелось...

Spoiler
main.cpp
  
struct myStruct {};
template class EEprom<myStruct>;
EEprom<myStruct> ee;

test.cpp
  
#include main.h			//to know myStruct
extern template class EEprom<myStruct>;
extern EEprom<myStruct> ee;

 

Но по идее можно спрятать эти два extern в main.h и получится, что main.cpp, как бы, экспортирует свою функциональность...

to EdgeAligned - специально собрали проект? спасибо) я только не могу сообразить, что вы хотели сказать. Как эта логика поможет мне растащить эти два инстанса по разным модулям и там их пользовать?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Если класс Eeprom подразумевает функционал чтения/записи микросхемки еепром, то ему, этому классу, вообще не нужен разный тип шаблона. Как я писал ранее, для общения с еепром нужно лишь указать начальный адрес и кол-во байт. А вот эти значения должны уже выбираться в том классе, который реализует конкретную структуру представления данных.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Мне показалась очень интересной идея таскать везде коробочку ee, чтобы она уже содержала в себе структуру с нужными данными, чтобы не разыменовывать указатели после ee.getPntr(). В общем не такая уж и проблема, но хотелось красаты. Ну и само по себе - ни какая-то сверхестественная хотелка.
А eeprom будет эмулировать на флеше, поэтому нужно знать размер структуры, которая потом будет копироваться (шагать) по странице. Вообще я хочу запилить библиотеку для ардуинки, Такого функционала нет нигде. Отсюда и желание спрятать это в максимально простые EEprom<strct> ee; ee.begin(); ee.commit(); ee.data

В вашем примере на скрине, если нарисовать рядом test.cpp, как в нем обратиться к objA.Write(&myInt)? Мне кажется начнется та же история.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Ну во-первых, не обязательно прям создавать объекты, чтобы получить доступ к фукнционалу класса. Есть static методы, к которым можно обратиться без создания объекта, просто через имя_класса::имя_метода(входные_параметры). И тогда достаточно обычного #include "файл.h". 

Во-вторых, если рассматривать то, что я на скрине привел, то видно, что класс ClassA в своем Foo() имеет обращение к методу его базового класса: this->Read(...). А если при наследовании Eeprom заменить private на public, то метод Read получится вызывать даже из objA.Read(). Но для такого варианта применения все эти замуты вообще какбы не нужны. Просто подключайте класс Eeprom куда нужно и используйте в нем static методы без создания объекта класса. 

Тут как бы вопрос не в инструментах языка, а вообще в структуре построения программы. Да, С++ заставляет использовать иной подход к сотворению кода. 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

так мне не нужны static методы. У меня 2 инстанса (или больше). Идея как раз в том, чтобы каждый знал свои эксклюзивные параметры. И еще одна важная необходимость - легко таскать это по разным cpp файлам. Для того и писалось, собственно.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Наткнулся сегодня на этот пост. Говорят, что неиспользование extern template myClass<type> ведет только к увеличению времени компиляции и размера объектного файла. И после линковки всё равно останется только одна копия функций для каждого типа. 
Решил сегодня на свежую голову всё перепроверить - действительно так. Вчера, видимо, где то оставлял копию с другим типом и собирался двойной объем.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

По ссылке там обсуждается другое. А у вас - не просто класс, а созданный объект класса, то есть переменная ee1 с типом класса EEprom<>.

Если для неё не будете использовать extern, то переменная ee1 не будет доступна в другом файле. По аналогии с обычным Си. 

Но формат объявления переменной, хоть даже и extern, он содержит имя типа и имя переменной. В вашем случае имя типа - это класс. Поэтому вот так (хотя лично я сделал бы иначе) :

image.png

Изменено пользователем EdgeAligned
Добавил скрин

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

57 minutes ago, EdgeAligned said:

Поэтому вот так (хотя лично я сделал бы иначе) :

Вы просто говорили сначала не использовать extern, а использовать наследование... Я спросил, как это поможет достать мой объект в другом модуле. Вы теперь говорите, что нужен extern. И если можно как-то иначе - как?

Сейчас пересобрал свой проект - всё таки выводы были верными - компилятор у меня не выкидывает автоматически из бинарника повторяющийся код функций. То есть для каждого объектного файла создается копия класса и это всё уходит в прошивку... Использование template class/extern template class решает эту проблему. Но выглядит некрасиво.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...