Шаблон для обмена данными между объектами в C++
я начал миграцию алгоритма физики высоких энергий, написанного на FORTRAN, в объектно-ориентированный подход на C++. Код FORTRAN использует множество глобальных переменных во многих функциях.
я упростил глобальные переменные в набор входных переменных и набор инварианты (переменные вычисляются один раз в начале алгоритма, а затем использовать все функции).
кроме того, я разделил весь алгоритм состоит из трех логических шагов, представленных тремя различными классами. Итак, очень просто, у меня есть что-то вроде этого:
double calculateFactor(double x, double y, double z)
{
InvariantsTypeA invA();
InvariantsTypeB invB();
// they need x, y and z
invA.CalculateValues();
invB.CalculateValues();
Step1 s1();
Step2 s2();
Step3 s3();
// they need x, y, z, invA and invB
return s1.Eval() + s2.Eval() + s3.Eval();
}
моя проблема:
- для выполнения расчетов все
InvariantsTypeX
иStepX
объекты нуждаются во входных параметрах (и это не только три). - три объекта
s1
,s2
иs3
нужны данныеinvA
иinvB
объекты. - все классы используют несколько другие классы через композицию, чтобы сделать свою работу, и все эти классы также нуждаются в вводе и инварианты (например,
s1
объект-членtheta
классаThetaMatrix
что нужноx
,z
иinvB
чтобы построить). - я не могу переписать алгоритм для уменьшения глобальных значений, потому что он следует нескольким формулам физики высоких энергий, и эти формулы точно такие же.
есть ли хороший шаблон для обмена входные параметры и инварианты для всех объектов, используемых для расчета результата?
должен ли я использовать синглеты? (но calculateFactor
функция оценивается около миллиона раз)
или я должен передать все необходимые данные в качестве аргументов объектам при их создании?(но если я это сделаю, то данные будут передаваться везде, в каждом объекте член каждого класса, создавая беспорядок)
спасибо.
6 ответов
Ну, в C++ наиболее подходящее решение, учитывая ваши ограничения и условия, представлен указатели. Многие разработчики сказали вам использовать boost:: shared_ptr. Ну это не обязательно, хотя он обеспечивает лучшую производительность, особенно при рассмотрении переносимости и надежности к сбоям системы.
вам не нужно связываться с boost. Это правда, что они не компилируются и что теперь процессы стандартизации приведут к c++ С напрямую интегрированным boost как стандартная библиотека, но если вы не хотите использовать внешнюю библиотеку, вы, очевидно, можете.
Итак, давайте попробуем решить вашу проблему, используя только C++ и то, что он предоставляет на самом деле.
у вас, вероятно, будет основной метод, и там, как вы сказали раньше, инициализируйте все инвариантные элементы... таким образом, у вас в основном есть константы, и они могут быть любого возможного типа. нет необходимости делать их постоянными, если вы хотите, однако, в основном вы создаете свои инвариантные элементы и указываете их для всех тех компонентов, которые требуют их использования. Сначала в отдельном файле под названием "common_components.hpp " рассмотрим следующее (Я предполагаю, что вам нужны некоторые типы для ваших инвариантных переменных):
typedef struct {
Type1 invariant_var1;
Type2 invariant_var2;
...
TypeN invariant_varN;
} InvariantType; // Contains the variables I need, it is a type, instantiating it will generate a set of global variables.
typedef InvariantType* InvariantPtr; // Will point to a set of invariants
в вашем " main.cpp " файл у вас будет:
#include "common_components.hpp"
// Functions declaration
int main(int, char**);
MyType1 CalculateValues1(InvariantPtr); /* Your functions have as imput param the pointer to globals */
MyType2 CalculateValues2(InvariantPtr); /* Your functions have as imput param the pointer to globals */
...
MyType3 CalculateValuesN(InvariantPtr); /* Your functions have as imput param the pointer to globals */
// Main implementation
int main(int argc, char** argv) {
InvariantType invariants = {
value1,
value2,
...
valueN
}; // Instantiating all invariants I need.
InvariantPtr global = &invariants;
// Now I have my variable global being a pointer to global.
// Here I have to call the functions
CalculateValue1(global);
CalculateValue2(global);
...
CalculateValueN(global);
}
Если у вас есть функции, возвращающие или использующие глобальную переменную, используйте указатель на структуру, изменяющую интерфейс методов. При этом все изменения будут затоплены все через переменные тосс.
Почему бы не передать инварианты в качестве параметра функции или конструктору класса, имеющего метод calculateFactor ?
также попробуйте собрать параметры вместе, если у вас слишком много параметров для одной функции (например, вместо (x, y, z) передать 3D-точку, у вас есть только 1 параметр вместо 3).
три логических шага, представленных тремя различными классами
возможно, это был не лучший подход.
один класс может иметь большое количество "глобальных" переменных, общих для всех методов класса.
что я сделал при преобразовании старых кодов (C или Fortran) в новые структуры OO, это попытаться создать один класс, который представляет собой более полную "вещь".
в некоторых случаях хорошо структурированный FORTRAN будет используйте" именованные общие блоки " для кластеризации вещей в значимые группы. Это намек на то, что "вещь" действительно был.
кроме того, FORTRAN будет иметь много параллельных массивов, которые на самом деле не являются отдельными вещами, они являются отдельными атрибутами общей вещи.
DOUBLE X(200)
DOUBLE Y(200)
- это действительно небольшой класс с двумя атрибутами, которые вы бы поместили в коллекцию.
наконец, вы можете легко создавать большие классы только с данными, отдельно от класса, который содержит функции, которые выполняют работу. Это своего рода жутко, но это позволяет вам утончить общую проблему, переведя общий блок в класс и просто передав экземпляр этого класса каждой функции, которая использует COMMON.
существует очень простой класс шаблона для обмена данными между объектами в C++, и он называется shared_ptr. Он находится в новом STL и в boost.
Если два объекта имеют shared_ptr к одному и тому же объекту, они получают общий доступ к любым данным, которые он содержит.
в вашем конкретном случае вы, вероятно, не хотите этого, но хотите простой класс, который содержит данные.
class FactorCalculator
{
InvariantsType invA;
InvariantsType invB;
public:
FactorCalculator() // calculate the invariants once per calculator
{
invA.CalculateValues();
invB.CalculateValues();
}
// call multiple times with different values of x, y, z
double calculateFactor( double x, double y, double z ) /*const*/
{
// calculate using pre-calculated values in invA and invB
}
};
вместо передачи каждого параметра по отдельности создайте другой класс для их хранения и передайте экземпляр этого класса:
// Before
void f1(int a, int b, int c) {
cout << a << b << c << endl;
}
// After
void f2(const HighEnergyParams& x) {
cout << x.a << x.b << x.c << endl;
}
первый пункт: глобалы не почти как плохо (в себе), как много (большинство?) программисты утверждают. На самом деле, сами по себе они не так уж плохи. Они в первую очередь являются симптомом других проблем, в первую очередь 1) логически отдельные части кода, которые были излишне перемешаны, и 2) код, который имеет ненужные зависимости данных.
в вашем случае это звуки как уже устранены (или, по крайней мере, сведены к минимуму) реальные проблемы (будучи инварианты, а не переменные, сами по себе устраняют один основной источник проблем). Вы уже заявили, что не можете устранить зависимости данных, и вы, по-видимому, не смешали код до такой степени, что у вас есть по крайней мере два разных набора инвариантов. Не видя кода, это может быть более грубая гранулярность, чем действительно необходимо, и, возможно, при ближайшем рассмотрении некоторые из этих зависимостей могут быть полностью устранены.
Если вы можете уменьшить или устранить зависимость-это стоящее занятие , но устранение глобалов само по себе редко бывает стоящим или полезным. На самом деле, я бы сказал, что за последнее десятилетие или около того я видел меньше проблем, вызванных глобалами, чем людьми, которые на самом деле не понимали своих проблем, пытаясь устранить то, что было (или должно было быть) прекрасно как глобалы.
учитывая, что они предназначены для инвариантности, что вы, вероятно,должны do является принудительным, что явно. Например, имейте класс фабрики (или функцию), который создает инвариантный класс. Инвариантный класс делает фабрику своим другом, но это только способ изменения членов инвариантного класса. Класс factory, в свою очередь, имеет (например) статическое bool
, и выполняет assert
Если вы попытаетесь запустить его несколько раз. Это дает (разумный уровень) уверенность в том, что инварианты действительно инвариантны (да,reinterpret_cast
позволит вам изменять данные в любом случае, но не авария.)
единственный реальный вопрос, который у меня был бы, - есть ли реальная точка в разделении ваших инвариантов на два "куска", если все вычисления действительно зависят от обоих. Если есть четкое, логическое разделение между ними, это здорово (даже если они привыкнут вместе). Однако, если у вас есть логически единый блок данных, попытка разбить его на части может быть контрпродуктивной.
итог: глобалы (в худшем случае) являются симптомом, а не болезнью. Настаивая на том, что вы going понизить температуру пациента до 98,6 градуса может быть контрпродуктивно-особенно если пациент животное, нормальная температура тела которого на самом деле 102 градуса.