LINUX.ORG.RU

Вопрос по дизайну/архитекуте .hpp файлов (кого куда и как включать)

 


1

1

Есть некая библиотека (моя), солянка из разных, местами слабо связанных фрагментов кода.

Появилась необходимость заюзать кусочек библиотеки в проекте, при этом хочется выделить туда минимально необходимую часть библиотеки (что бы лишнего не тащить) просто методом копирования (что бы проще было накатывать исправления и т.д.).

В библиотеке есть файл dump.hpp (условно) обеспечивающий запись разных объектов в бинарном формате, и он входит в ту часть которую нужно копировать. Этот файл определяет какие то служебные (шаблонные) методы а потом их как то специализует по необходимости для всего что есть в библиотеке. Понятно что он инклюдит кучу всего, в том числе кучу того что копировать не хочется. Вопрос - че с этим делать?

Я вижу два решения, оба кривые:

  1. перенести части dump.hpp работающие с конкретными объектами в .hpp файлы где эти объекты объявлены. Минусы - все же этот код лучше выглядит когда лежит кучно, кроме того все станет зависеть от dump.hpp (сейчас это не так и хотелось бы что бы оно так и осталось).

  2. понатыкать в dump.hpp директив условной компиляции, т.е. что бы соотве фрагмент кода для объекта A включался только если .hpp файл с этим объектом уже был включен. Это немного странно выглядит, dump.hpp всегда должен будет выключаться последним.

cast @pon4ik, @monk

★★★★

Последнее исправление: cetjs2 (всего исправлений: 1)

Ответ на: комментарий от Begemoth

И как этим в итоге пользоваться? Включать потом dump_A руками туда где он нужен? Но это же трындец…

AntonI ★★★★
() автор топика

Почему не сделать просто:

  • dump.hpp – «Этот файл определяет какие то служебные (шаблонные) методы»
  • dump_spec.hpp – «их как то специализует по необходимости для всего что есть в библиотеке.»
xaizek ★★★★★
()
Ответ на: комментарий от xaizek

А в самой библиотеке юзать dump_spec? Ок, спасибо, это третий вариант о котором я не думал;-)

AntonI ★★★★
() автор топика

Специализации под конкретные объекты - можно и нужно делать в единице трансляции которая содержит тонкости реализации этих объектов.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

А, не, херню сморозил, так только с функциями сработает. Плюс ты почему-то не хочешь, что бы класс диктовал как его будут дампить, хм.

Тогда и правда кроме как вынести все специализации в отдельный хидер[ы] ничего не придумывается.

Интересно - а чем твой dump, идеологически отличается от std::ostream& operator <<?

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Хотя не, с классами тоже можно, но выйдет многобуков:

//dump.hpp

#include <iostream>

//generic case
template <typename T>
struct Dump {
    Dump(const T& d) {
        std::cout << "Generic case:" << d << std::endl;
    }
};

// forward declarations

// specializations
template <> struct Dump<std::string> {
    Dump(const std::string&s);
};
// string_dump.cpp || all_special_dumps.cpp

#include "dump.hpp"
#include <string>
Dump<std::string>::Dump(const std::string& d) {
    std::cout << "String case:" << d << std::endl;
}
// main.cpp

#include "dump.hpp"

int main(int argc, char* argv[]){
    Dump<int>(43);
    Dump<std::string>("42");
    return 0;
}

Из плюсов такого подхода:

  • код специализаций сокрыт
  • код специализаций содержится в библиотеке а не в каждом её клиенте
  • код специализаций можно разделять между двумя бинарниками (пример либа A зависит от dump, либа B зависит от dump, приложение зависит от A и B)
pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)

Это конечно оффтопик, кто как хочет тот так и рукоблудит… Но имхо, делать либу и потом что-то оттуда размножать копированием, это как построить дом, а жить то в цыганском таборе, то у соседки то в палатке рядом с домом, а дом использовать в качестве сарая.

pon4ik ★★★★★
()
Ответ на: комментарий от AntonI

Включать потом dump_A руками туда где он нужен? Но это же трындец…

В C++ разве не везде так?

bhfq ★★★★★
()
Ответ на: комментарий от pon4ik

а чем твой dump, идеологически отличается от std::ostream& operator <<?

Тем что он бинарный (я пререгружаю одну птичку < для своих потоков:-)). Ну и ещё там сначала заголовок, потом данные, причем заголовок м.б.. сложный с описанием структуры ячейки контейнера.

Я все ещё не очень понял:-(

Давай чуть абстрактней, вот у меня есть разные классы A, B, C… в разных хедерах. И есть разные функции f, g, h… в разных хедерах (это не обязательно прямо вот функции, какой то функционал, че то можно делать). Каждая функция требует какой то специализации для каждого класса. Где размещать специализации что бы не:

  1. Увеличивалась связность (т.е. что бы импорт А не тащил за собой все f,g,h и импорт f не тащил за собой все A, B, C)

  2. При этом юзеру не приходилось писать инклюды на каждый чих? Типа там fA, gC и т.д. руками подключать? Т.е. если я подключил f и B то у меня автоматом проинклюдилась специализация fB.

AntonI ★★★★
() автор топика
Последнее исправление: AntonI (всего исправлений: 3)
Ответ на: комментарий от pon4ik

Я может не прав конечно, но это можно рассматривать не как либу а как цельный фреймворк с кучей зависимостей. А в проекте нужен небольшой фрагмент, потому что этот проект аналогичный толстый фреймворк во многом дублирующий мой (но по другому и в другой области), но я написал некоторую важную часть на фрагменте своего фреймворка и переписывать не хочу. Заказчику пофик, но тащить все мое вырвиглазие некий перебор - там 99% будет лишним, у них и своего вырвиглазия хватает:-)

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Может быть тебе поможет такой приём:

// f.h
template <typename T>
void f();

// a.h
class A {};

// fA.cpp
#include "a.h"
#include "f.h"

template <>
void f<A>() {}

// main.cpp
#include "f.h"
#include "a.h"

int main() {
    f<A>();
}

Как видно из примера, в main не иклудится специализация f<A> (нет никакого fA.h), но код компилируется, потому что линкер находит тело функции в fA.cpp. Это работает, но с ограничениями: у f в f.h не должно быть тела (иначе ошибка линковки: две реализации функции) и специализация должна быть полная.

Marvel
()
Ответ на: комментарий от AntonI

Объявление(declaration) специализаций в dump.hpp, хидеры классов и функций не подключаются, вместо этого используется forward declaration. Определения(definition) специализаций - либо в dump_special.cpp, либо в a.cpp, b.cpp, e.t.c - но тебе второй вариант не понраву, т.к. это повышает связанность, что в принципе вполне Ок и зависит от целей и задачей.

Таким образом ты сократишь количество кода даже в текущих проектах(на уровне бинарников) и избежишь изысканных ошибок линковки, при этом dump.hpp будет вполне себе копипабельным. Из недостатков чуть больше буков(но один раз).

pon4ik ★★★★★
()
Ответ на: комментарий от AntonI

Я исхожу вот из чего - если вырвиглазие каждый раз копировать, оно так и останется вырвиглазием. А если начать его интеграции оно само итеративненько станет API, все места типа описанного тобой рано или поздно всплывут, ну на лоре почаще народ подёргаешь, там не так уж и много приёмов, что бы всё счастье привести в порядок.

Но на старте - это дороже бесспорно.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

При таком подходе - клиент знает про наличие твоих специальных классов, но будет получать по рукам при попытке их использования.

pon4ik ★★★★★
()
Ответ на: комментарий от AntonI

Алыверды, на всякий если ты не в курсе. Либа и фреймворк отличаются не размером. Либа это набор API для решения класса задач, а фреймворк это набор API и инструментов, которые диктуют процесс проектирования и разработки.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Объявление(declaration) специализаций в dump.hpp, хидеры классов и функций не подключаются, вместо этого используется forward declaration. Определения(definition) специализаций - либо в dump_special.cpp, либо в a.cpp, b.cpp, e.t.c

С .cpp к сожалению не взлетит - там шаблоны, инстацирует их юзер (@Marvel, спасибо за идею - но увы).

У меня сейчас такая безумная мысль возникла.

#ifndef A_HPP
#define A_HPP

template <int D, typename T> struct A{ ... };

#ifdef F_HPP

#include "f.hpp"

#endif // F_HPP
#endif // A_HPP
#ifndef F_HPP
#define F_HPP

struct F{ ... };

#endif // F_HPP
#ifdef A_HPP
#ifndef FA_HPP

template <int D, typename T> F& operator ^ (F&, const A<T, D>& a){ ... }; // такого кода мало

#endif //FA_HPP
#endif //A_HPP

тогда если включить a.hpp работает только A, если включить f.hpp работает только F, если включить их оба (в любом порядке) - работают оба.

Не знаю насколько это криво смотрится.

AntonI ★★★★
() автор топика
Ответ на: комментарий от pon4ik

Алыверды, на всякий если ты не в курсе. Либа и фреймворк отличаются не размером. Либа это набор API для решения класса задач, а фреймворк это набор API и инструментов, которые диктуют процесс проектирования и разработки.

Спасибо, я догадывался но точно не знал (не мог так четко сформулировать).

У меня есть некая рекомендуемая линия разработки под которую заточено много плюшек (включая шаблон для сборки gnu make), но она не является догматичной. С-но я специально старался минимизировать связность хедеров, потому что коллеги не любят лишние икнлюды (и я тоже). Многие модули вообще низависят ни от чего, выдирай и юзай отдельно (например со всякими ништяками для отладки).

Но что то цепляет все + много модулей на питоне. Так что я не знаю как это правильно назвать.

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

С .cpp к сожалению не взлетит - там шаблоны

Я выше скинул рабочий код где взлетело же:) Зачем давать инстанцировать юзеру то, что можно инстанцировать самому да ещё и с нужными флагами оптимизации.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

В смысле в моём коде не было холостого инстанцирования, но это же ещё плюс одна строчка на специализацию и один фейковый символ. При желании наверное можно это свести к CRTP и меньше буков писать.

pon4ik ★★★★★
()
Последнее исправление: pon4ik (всего исправлений: 1)
Ответ на: комментарий от pon4ik

Зачем давать инстанцировать юзеру то, что можно инстанцировать самому

Затем что я не знаю с какими параметрами он чего будет инстацировать. Если речь о контейнере, то параметр инстацирования вообще его структура а не моя…

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

А, это настолько обобщённое программирование… Тогда у тебя или есть набор трейтов, или всё равно покрыты только какие-то частные случае которые можно частично специализировать. С частичной специализацией подход похоже не работает, надо подумать.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Чет я не соображу. Покажи пример когда у тебя есть специализация, но инстанцировать её обязательно должен пользователь. Мы же говорим всё ещё про dump?

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

например

struct F { ... };

template <int D, typename T> F& operator ^ (F &f, const A<D,T> &a){...}

F и A мои типы, T что то из int, double, float, complex, D больше нуля. Число всех возможных инстацирований счетно но велико (а некоторые пользователи умеют удивлять;-)).

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Такое да только в отдельный хидер выйдет вынести. Но вот специализации поддерживаемых из коробки листов подозреваю всё же конечны и всё равно записаны вручную(я так понимаю ниже вызывается та же ^ для укладывания листов в поток во время итерации по контейнеру). Вот их бы прятать в реализацию и инстанцировать на стороне библиотеки.

Разница тут в чём - которые ты обернул в дамп, это твоя реализация, ты можешь её оптимизировать, а листы которые обернул пользователь это его реализация, и его ответственность если он хочет эту реализацию переиспользовать.

С другой стороны оно и в хидерах будет работать, просто код клиентских бинарников будет немного пухнуть.

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

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Разница тут в чём - которые ты обернул в дамп, это твоя реализация, ты можешь её оптимизировать, а листы которые обернул пользователь это его реализация, и его ответственность если он хочет эту реализацию переиспользовать.

Я неправильно выразился наверное. Оператор ^ перегружен для разных классов (моих) и вопрос о размещении этих перегрузок в первую очередь.

Как юзер F^ для своих классов делает это его проблемы, хотя я даю ему некий механизм (макрос) что бы сделать это красиво + стоит заглушка которая при компиляции говорит что где то этот оператор был забыт.

Ну и с ^ это только одна из таких проблем, там несколько похожих вещей.

Так что ты думаешь вот про такое решение? Вопрос по дизайну/архитекуте .hpp файлов (кого куда и как включать) (комментарий)

AntonI ★★★★
() автор топика
Ответ на: комментарий от pon4ik

если вырвиглазие каждый раз копировать, оно так и останется вырвиглазием.

Что бы оно перестало быть вырвиглазием мне нужно научится писать нормальный код а не вырвиглазие;-)

«не стреляйте в пианиста - он играет как умеет»(с)

AntonI ★★★★
() автор топика
Ответ на: комментарий от AntonI

Ну тут такое неявное требование возникает включать дамп после всех остальных хидеров. Так-то в принципе работать будет. Если взвешивать с решением «все остальные специализации в отдельном файле» - то наверное это лучше в силу эффективности но интерфейс - хуже.

pon4ik ★★★★★
()
Ответ на: комментарий от AntonI

Так код то нормальный на низком уровне. Вот организовать бы его теперь в API которое не нужно размножать методом копирования и глядишь потом запихается в чё нить типа буста.

pon4ik ★★★★★
()
Ответ на: комментарий от pon4ik

Так код то нормальный на низком уровне.

Царь и прочие икспертды ругали:-)

Вот организовать бы его теперь в API

Я с каждым годом все больше люблю физику и все меньше программирование… Хорошо бы, да. Вот получу толстый грант и найму кого нить кто умеет такие вещи.

AntonI ★★★★
() автор топика
Ответ на: комментарий от pon4ik

Ну тут такое неявное требование возникает включать дамп после всех остальных хидеров.

Ну вроде я как раз это смог победить? Такое требование у меня есть в другом (аналогичном) месте, и я об это раз в году бьюсь. Надо будет вот так вот переделать видимо.

Ну или перестать бояться лишних инклюдов.

AntonI ★★★★
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.