Шаблон для обмена данными между объектами в С++

Я начал миграцию алгоритма физики высоких энергий, написанного в FORTRAN, на объектно-ориентированный подход на С++. Код 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 оценивается примерно миллион раз)

Или мне нужно передать все необходимые данные в качестве аргументов для объектов при их создании? (но если я это сделаю, то данные будут передаваться везде в каждом объекте-члене каждого класса, создавая беспорядок)

Спасибо.

Ответ 1

Хорошо, в С++ наиболее подходящее решение, учитывая ваши ограничения и условия, представлено указателями. Многие разработчики сказали вам использовать boost:: shared_ptr. Ну, это не обязательно, хотя оно обеспечивает лучшую производительность, особенно если учитывать переносимость и надежность системных сбоев.

Вам не нужно связываться с boost. Это правда, что они не скомпилированы и что теперь процессы стандартизации приведут к тому, что С++ с boost напрямую интегрируется как стандартная библиотека, но если вы не хотите использовать внешнюю библиотеку, вы, очевидно, можете.

Итак, отпустите и попытайтесь решить вашу проблему, используя только С++ и то, что она обеспечивает.

У вас, вероятно, будет основной метод, и там, как вы сказали ранее, инициализируйте все элементы инвариантов... поэтому у вас в основном есть константы, и они могут быть всевозможными типами. нет необходимости делать их постоянными, если вы хотите, однако в основном вы создаете свои инвариантные элементы и указываете их для всех тех компонентов, которые требуют их использования. Сначала в отдельный файл под названием "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);
}

Если у вас есть функции, возвращающие или использующие глобальную переменную, используйте указатель на интерфейс, модифицирующий ваш метод. Таким образом, все изменения будут затоплены для всех с использованием переменных thoss.

Ответ 2

Почему бы не передать инварианты в качестве параметра функции или конструктору класса, имеющего метод calculateFactor?

Также попробуйте собрать параметры вместе, если у вас слишком много параметров для одной функции (например, вместо (x, y, z) пройти трехмерную точку, у вас есть только 1 параметр вместо 3).

Ответ 3

Существует очень простой шаблонный класс для обмена данными между объектами в С++ и называется 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
   }
};

Ответ 4

три логических шага, представленные тремя разными классами

Это, возможно, не лучший подход.

Один класс может иметь большое количество "глобальных" переменных, разделяемых всеми методами класса.

То, что я сделал при преобразовании старых кодов (C или Fortran) в новые структуры OO, - это попытаться создать один класс, который представляет собой более полную "вещь".

В некоторых случаях хорошо структурированный FORTRAN будет использовать "Именованные COMMON Blocks" для кластеризации вещей в значимые группы. Это намек на то, что действительно было на самом деле.

Кроме того, FORTRAN будет иметь множество параллельных массивов, которые на самом деле не являются отдельными вещами, они являются отдельными атрибутами общей вещи.

DOUBLE X(200)
DOUBLE Y(200)

Действительно маленький класс с двумя атрибутами, которые вы поместили в коллекцию.

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

Ответ 5

Первая точка: глобалы не так плохи (сами по себе), как утверждают многие (большинство?) программистов. На самом деле, они сами по себе не совсем плохие. Они в первую очередь являются симптомом других проблем, в первую очередь 1) логически разделяют фрагменты кода, которые были излишне смешаны, и 2) код, который имеет ненужные зависимости данных.

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

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

Учитывая, что они предназначены для того, чтобы быть инвариантными, то, что вы, вероятно, должны делать, это принудительное применение этого явно. Например, у вас есть класс factory (или функция), который создает инвариантный класс. Инвариантный класс делает его factory его другом, но единственный способ, которым могут изменяться члены инвариантного класса. Класс factory, в свою очередь, имеет (например) статический bool и выполняет assert, если вы попытаетесь запустить его более одного раза. Это дает (разумный уровень) уверенность в том, что инварианты действительно являются инвариантными (да, a reinterpret_cast позволит вам изменять данные в любом случае, но не случайно).

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

Нижняя линия: глобалы (в худшем случае) являются симптомом, а не болезнью. Настаивая на том, что вы собираетесь получить температуру пациента до 98,6 градусов, может быть контрпродуктивным - особенно если пациент является животным, нормальная температура тела которого составляет 102 градуса.

Ответ 6

Вместо того, чтобы передавать каждый параметр отдельно, создайте еще один класс, чтобы сохранить их все и передать экземпляр этого класса:

// 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;
}

Ответ 7

ммм. Cpp не обязательно объектно-ориентированный. Это ГТА программирования! Вы свободны быть уродом Object obscessed, программистом на релакс C, функциональным программистом, кем угодно; микс боевых искусств.

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

Давайте поймем, почему глобальные переменные могут вызвать проблемы. Как вы знаете, переменные - это состояние программы, а состояние - это душа программы. Плохое или неправильное состояние вызывает ошибки времени выполнения и логические ошибки. Проблема с глобальными переменными/глобальным состоянием заключается в том, что любая часть нашего кода имеет к ней доступ; таким образом, в случае недопустимого состояния их следует рассматривать как плохих парней или преступников, то есть функций и операторов. Однако это применимо, только если вы действительно использовали так много функций для вашей глобальной переменной. Я имею в виду, что ты единственный, кто работает над своей одинокой программой. Глобальные переменные представляют собой реальную проблему, только если вы делаете командный проект. В этом случае многие люди имеют к нему доступ, пишут различные функции, которые могут или не могут получить доступ к этой переменной.