C++ automatic finalization or objects destruction

166 views Asked by At

In this example I faced the problem of copying the code:

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 here is a bad code structure. If I have a lot of constructs, the width of the code will be too large, this is also bad:

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

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

How can I save good code sturcture and solve code copying? Finalize1/2/3 is a API functions and not my program classes. Maybe some STL containers can solve it? Maybe something like that?

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();
}
3

There are 3 answers

0
Marshall Clow On BEST ANSWER

Why not use real objects?

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

and so on for part 2, 3, 4, etc. Now your example looks like this:

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

     // some code ...
        }
    catch { const std::runtime_error &ex ) {
        std::cerr << "GoodExample Failed: " << ex.what << std::end;
        }
    }
0
Christopher Yeleighton On

Whenever you get a precious resource from an API, you need to wrap it as an object with the appropriate destructor. So, if Initialize1 initialises something1 then something1 should really be an object Something1 that knows how to initialise and how to finalise itself. Also, a failure to initialise should throw an exception (this is not done with fstream because fstream is older than this concept).

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

You could streamline Marshall's suggestion and use the not-yet-standardized std::make_unique_resource() (this function is closely related to scope_guard, a contraption suggested by Andrei Alexandrescu some years ago and also in that proposal). That gives you an object with two functions - one to run at the variable scope's start, another to run at its end (i.e. on construction and destruction respectively).

Then, instead of defining four separate classes, you'd just write:

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
}

The proposal has code for the implementation; and - it's not complex at all. So you could just copy the implementation and create your own not_std::make_unique_resource() function and related templated class(es).