Шаблон для обмена данными между объектами в C ++ - PullRequest
10 голосов
/ 25 января 2011

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

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

Спасибо.

Ответы [ 7 ]

2 голосов
/ 26 января 2011

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

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

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

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

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

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

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

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

В некоторых случаях, хорошо структурированный FORTRAN будет использовать «Именованные ОБЩИЕ блоки» для кластеризации вещей в значимые группы.Это намек на то, чем на самом деле была «вещь».

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

DOUBLE X(200)
DOUBLE Y(200)

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

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

2 голосов
/ 25 января 2011

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

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

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

1 голос
/ 25 января 2011

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

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

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

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

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

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

0 голосов
/ 14 июня 2019

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

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

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

0 голосов
/ 25 января 2011

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

// 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;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...