Две версии программы в зависимости от входного параметра без дублирования кода

#c

Вопрос:

Я решаю следующую проблему. Я работаю над программой оптимизации на C , которая в зависимости от начальных настроек пользователя использует различные правила (стандарты) для расчета целевой функции. Предположим, что у нас есть метод A, основанный на некоторой норме, и метод B, основанный на другой норме, для вычисления целевой функции. Пользователь устанавливает правильный стандарт перед запуском программы. Остальная часть кода та же самая. Во время оптимизации целевая функция вызывается итеративно снова и снова. Конечно, есть простое решение: каждый раз, когда вызывается целевая функция, условие IF используется для определения того, какой стандарт использовать. Но поскольку программе приходится принимать решения на каждой итерации, она кажется неэффективной. Второй вариант-создать 2 независимых кода и запустить только один с требуемым стандартом. Это, в свою очередь, уродливо с точки зрения дублирующего кода.

Я представил, что создам 2 разных класса и буду использовать выбранный класс, используя условие IF при построении объекта. Это заставило бы программу принять решение только один раз при создании объекта, но во время самой итерации объект будет четко определен. К сожалению, это не работает, потому что объекты не могут быть созданы в условиях IF.

 //-----------------------------------------------------------
// Create object sensor based on input
if(data.sensors_tipe == "Uniaxial_025") Sensor_Uniaxial_025 sensor(data);
else if (data.sensors_tipe == "T_rosette_05") Sensor_T_rosette_05 sensor(data);
else report.error("some error");

// rotation test
int element_index = 1;
double orientation_angle = 3.490658503988659;
sensor.rotate(element_index, orientation_angle);
 

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

Я новичок, и я нигде не нашел ответа. Так что, может быть, кто-нибудь сможет помочь. Спасибо

Ответ №1:

Это хорошая работа для шаблонов, которые являются «рецептами» для создания кода.

Конечным результатом будет дублированный машинный код, но без дублирования в исходном коде.

 template<typename MethodT>
float optimize(const MethodTamp; method) {
  float v = method();

  // etc...
}


float methodA();
float methodB();

int main() {
  auto a = optimize(methodA);
  auto b = optimize(methodB);
}
 

Комментарии:

1. Обратите внимание, что для получения преимуществ от шаблонов (в отличие от параметров указателя функции) переданный функтор должен быть лямбдами или классами с operator() , в противном случае компиляторы вряд ли встроят вызов, что противоречит цели дублирования машинного кода.

2. @AlexGuteniev Это просто неверно. Все современные компиляторы будут тривиально оптимизировать регулярные функции так же, как они оптимизировали бы лямбду.

3. Здесь шаблон бесполезен, у вас есть optimize<float(*)()> в обоих случаях.

4. @Фрэнк, Да, я вижу, что ты прав. В производстве я все еще использую VS2015, который не может это оптимизировать, но современные компиляторы, включая VS2019, оптимизируют это ( godbolt.org/z/sfsP6dGKM )

Ответ №2:

Во-первых, решение с if может быть не таким уж плохим. Это ветвь при каждом вызове функции, но ветвь должна быть хорошо предсказана.

Во-вторых, если функции, реализующие метод A и метод B, достаточно велики, чтобы пропустить встраивание, используйте указатель функции.

В противном случае используйте статический полиморфизм с шаблонами, метод A и метод B могут передаваться через параметр шаблона в качестве функторов.

Ответ №3:

В случае, если пользователь может изменить стандарт после компиляции программы (например, перед каждым запуском), вы можете создать интерфейс и 2 дочерних элемента от него. Итак, при запуске вы должны создать экземпляр (один из 2), который вам нужен new . И тогда вы сможете им воспользоваться. Вы не можете использовать этот алгоритм с экземплярами стека.

Комментарии:

1. «Вы не можете использовать этот алгоритм с экземплярами стека». почему нет? Полиморфизм не обязательно требует динамического выделения: godbolt.org/z/s7PjT4YvP

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

3. И (имхо) он не хочет дублировать код вызовов функций для каждого типа датчиков. Так…

4. создание объекта с new помощью никогда не бывает хорошей идеей. Используйте интеллектуальные указатели для динамически выделяемых объектов, а не для необработанных указателей.

Ответ №4:

Один из способов — использовать наследование.

 class Sensor
{
public:
    virtual void rotate(int, double) = 0;
};

class Sensor_Uniaxial_025 : public Sensor
{
public:
    virtual void rotate(int, double) {/*stuff*/};
};

class Sensor_T_rosette_05 : public Sensor
{
public:
    virtual void rotate(int, double) {/*stuff*/};
};

Sensor* sensorToUse;

//-----------------------------------------------------------
// Create object sensor based on input
if(data.sensors_tipe == "Uniaxial_025") sensorToUse = new Sensor_Uniaxial_025(data);
else if (data.sensors_tipe == "T_rosette_05") sensorToUse = new  
Sensor_T_rosette_05(data);
else report.error("some error");

// rotation test
int element_index = 1;
double orientation_angle = 3.490658503988659;
sensorToUse->rotate(element_index, orientation_angle);
 

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

Альтернативой является использование шаблона. Смотрите другие ответы на эти подходы.

Комментарии:

1. Вам не нужно динамически выделять объекты для получения вызовов виртуальных функций. Вам нужен указатель или ссылка на базу, но это не обязательно подразумевает динамическое выделение. Смотрите здесь godbolt.org/z/s7PjT4YvP . И когда вы делаете динамическое выделение, вам все равно не нужны никакие new , кроме интеллектуальных указателей. Вам не хватает некоторых ; , кстати

2. Вы совершенно правы, но если ОП настаивал на том, чтобы сделать один foo звонок за пределами «если», то вам нужны либо указатели, либо обсуждение того, почему ссылки не могут быть переназначены, и я, как правило, не хочу добавлять новый материал в уже перегруженные операции. 🙂 Надеюсь, просто указание на способ ООП помогло им.

3. ОП говорит, что пользователь делает выбор в начале, а затем используется либо то, либо другое. Нет никаких причин помещать объект за пределы if . Другими словами, вы всегда можете поместить if вокруг всего кода в main, только для пользователей, которые должны быть сделаны до

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