C автоматическая доработка или уничтожение объектов

#c #stl #raii #code-structure #finalization

#c #stl #raii #структура кода #доработка

Вопрос:

В этом примере я столкнулся с проблемой копирования кода:

 void BadExample1() {
  if (!Initialize1())
    return;

  if (!Initialize2())  {
    Finalize1();
    return;
  }

  if (!Initialize3())  {
    Finalize1();
    Finalize2();
    return;
  }

  if (!Initialize4()) {
    Finalize1();
    Finalize2();
    Finalize3();
    return;
  }

  // some code..

  Finalize1();
  Finalize2();
  Finalize3();
  Finalize4();
}
  

Bnd здесь представляет собой плохую структуру кода. Если у меня будет много конструкций, ширина кода будет слишком большой, это тоже плохо:

 void BadExample2() {
  if (Initialize1()) {
    if (Initialize2()) {
      if (Initialize3()) {
        if (Initialize4()) {
          if (Initialize5()) {
            // some code..

            Finalize5();
          }
          Finalize4();
        }
        Finalize3();
      }
      Finalize2();
    }
    Finalize1();
  }
}
  

Как я могу сохранить хорошую структуру кода и решить проблему копирования кода?
Finalize1 /2 /3 — это функции API, а не мои программные классы.
Может быть, некоторые контейнеры STL могут решить эту проблему?
Может быть, что-то вроде этого?

 void GoodExample() {
  if (!Initialize1())
    return;
  RaiiWrapper<void(*)()> raii_wrapper1([]() {
    Finalize1();
  });

  if (!Initialize2())  {
    //Finalize1();
    return;
  }
  RaiiWrapper<void(*)()> raii_wrapper2([]() {
    Finalize2();
  });

  if (!Initialize3())  {
    //Finalize1();
    //Finalize2();
    return;
  }
  RaiiWrapper<void(*)()> raii_wrapper3([]() {
    Finalize3();
  });

  if (!Initialize4()) {
    //Finalize1();
    //Finalize2();
    //Finalize3();
    return;
  }
  RaiiWrapper<void(*)()> raii_wrapper4([]() {
    Finalize4();
  });

  // some code..

  //Finalize1();
  //Finalize2();
  //Finalize3();
  //Finalize4();
}
  

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

1. Да, именно так вы решаете эту проблему. Такие классы, как RaiiWrapper называются защитниками области . Их нет в стандартной библиотеке, но реализовать их тоже не сложно.

2. @HolyBlackCat: Смотрите мой ответ.

Ответ №1:

Почему бы не использовать реальные объекты?

 struct SetupPart1 {
   SetupPart1  () { if (!Initialize1() throw std::runtime_error("Part1"); }
   ~SetupPart1 () { Finalize1(); }
};
  

и так далее для части 2, 3, 4 и т.д.
Теперь ваш пример выглядит так:

 void GoodExample() {
    try {
        SetupPart1 p1;
        SetupPart2 p2;
        SetupPart3 p3;
        SetupPart4 p4;

     // some code ...
        }
    catch { const std::runtime_error amp;ex ) {
        std::cerr << "GoodExample Failed: " << ex.what << std::end;
        }
    }
  

Ответ №2:

Вы могли бы упростить предложение Маршалла и использовать еще не стандартизированную std::make_unique_resource() (эта функция тесно связана с scope_guard , хитроумным решением, предложенным Андреем Александреску несколько лет назад, а также в этом предложении). Это дает вам объект с двумя функциями — одна для запуска в начале области видимости переменной, другая для запуска в ее конце (т. Е. при создании и уничтожении соответственно).

Тогда вместо определения четырех отдельных классов вы бы просто написали:

 void GoodExample() {
    auto r1 = std::make_unique_resource(Initialize1, Finalize1);
    auto r2 = std::make_unique_resource(Initialize2, Finalize2);
    auto r3 = std::make_unique_resource(Initialize3, Finalize3);
    auto r4 = std::make_unique_resource(Initialize4, Finalize4);

    // some code
}
  

В предложении есть код для реализации; и — это совсем не сложно. Таким образом, вы могли бы просто скопировать реализацию и создать свою собственную not_std::make_unique_resource() функцию и связанные с ней шаблонные классы.

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

1. Технически бесполезно, пока оно не стандартизировано, но все же стоит проголосовать.

2. @user4581301: Это полезно — можно было бы просто реализовать в предложении, которое даже не такое длинное. См. Редактирование.

Ответ №3:

Всякий раз, когда вы получаете ценный ресурс из API, вам нужно обернуть его как объект с соответствующим деструктором. Итак, если Initialize1 инициализируется something1 , то something1 действительно должен быть объект Something1 , который знает, как инициализировать и как завершить себя. Кроме того, сбой инициализации должен вызвать исключение (это не выполняется с помощью, fstream потому что fstream старше этой концепции).

 class Something1 {
public: Something1 () { if (!Initialize1()) throw resource_failed ("1"); }
~Something1 () { Finalize1(); }
}