开发者

Elegant control flow in C++ (or any other language)

I am writing a function which has many return points (depending on what conditions are true) and it does a bunch of news at different points. At every return point, the set of deletes I need to do are different. And even then, I don't want to do them at different places. What is a good way to handle this?

Some details: Let us consider a function:

int MainClass::testFunction() {
  numThreads++;
  char *a = new char[100];
  // Some computation with a
  if (condition1) {
    numThreads--;
    return -1;
  } 
  char *b = new char[100];
  // Some computation with b
  if (condition2) {
    numThreads--;
    return 42;
  } 
  // Some more stuff. 
  numThreads--;
}

Now, before return -1, I need to do delete a while before return 42, I need to do delete a; delete b;. And you can imagine how this could get complicated if I had multiple return points.

Here are my solutions:

First solution: Put all deletes at the end of the function, put some labels, store the return value at return points, and use goto (yes, that dirty word!) to jump to appropriate deletes and return after executing those deletes.

In the above example, I could say

superset:
  delete b;
subset:
  delete a;
numThreads--;

And put goto superset before return 42 and goto subset before return -1.

I don't like the solution for obvious reasons! :-)

Second solution: I can build an inner class instance, and do new on variables of that class instance. Like this...

int MainClass::testFunction() {
  class Local {
    char *a, *b;
    Local () : a(NULL), b(NULL) {}
    ~Local () { if (a != NULL) delete a; if (b != NULL) delete b; }
  };
  Local l = Local();
  l.a = new char[100];
  // Some computation with a
  if (condition1) {
    return -1;
  } 
  l.b = new char[100];
  // Some computation with b
  if (condition2) {
    return 42;
  } 
}

So, what is the problem? Well, how do I access numThreads variable that was in the method? I want to do numThreads++ in the constructor for Local and numThreads-- in the dtor of Local. If it helps, testFunction is also a member function of another class.

Thanks for reading.

Update: Allocating the array on stack is definitely one possibility, but I have run into stack overflow (ah... the name开发者_如何学C of this website :-)) doing large allocations on stack (stack size for threads is 2MB by default). What I wanted to address in this question was general resource acquisition and destruction.


Read about RAII

Basically, never use new except in a constructor (or assignment), and never use delete except in a destructor.

Regarding your particular case, you can do the following:

(1) Just use std::vector<char>.
(2) Do char a[100]; instead (this is stack allocated, an therefore is automatically collected). This will only work if the 100 is a constant. You can also use std::array<char, 100> with a C++11 compiler (or boost::array<char, 100> if you have boost libraries installed).
(3) Use alloca() to allocate space on the stack (if 100 is not constant) (be careful with this).
(4) Write your own class which allocates memory using new in the constructor and deletes memory using delete in the destructor.

I would recommend (2) if 100 is a constant and not too big, otherwise (1).


What is a good way to handle this?

#include <vector>
int MainClass::testFunction() {
  numThreads++;
  std::vector<char> a(100);
  // Some computation with a
  if (condition1) {
    numThreads--;
    return -1;
  } 
  std::vector<char> b(100);
  // Some computation with b
  if (condition2) {
    numThreads--;
    return 42;
  } 
  // Some more stuff. 
  numThreads--;
}


Um. You're WAY overcomplicating this. Just set a and b to NULL at the beginning. Surround the whole thing with a try / catch. If anything goes wrong, throw something (anything will do). In the catch block, call delete on all of the variables. delete will do nothing if the variable is NULL.

And if you need to do stuff with return variables, you could theoretically just throw the return value (yes, you can throw ints). Then when you're done cleaning up, just return the thrown value.

char *a = NULL, *b = NULL;

try
{
    a = ...;
    if(bad_thing)
        throw -1;

    b = ...;
    throw 42;
}
catch(int e)
{
    delete[] a;
    delete[] b;
    return e;
}


int MainClass::imp_testFunction() {

// perhaps break up a-compute, b-compute, some more stuff to
// separate methods if they are nontrivial.

// 100 chars should go on the stack (something like boost::array
// is ideal if you can use that), unless you know you are
// performing deep/recursive calls or have a small stack
// (e.g. embedded). for large or variable sized allocations,
// use something like a std::vector.
    char a[100];

// Some computation with a
    if (condition1) {
        return -1;
    }

    char b[100];
// Some computation with b
    if (condition2) {
        return 42;
    }
// Some more stuff.
}

int MainClass::testFunction() {
   // refactor this so you don't need to inc/dec at each exit.
    ++numThreads;
    const int ret(imp_testFunction());
    --numThreads;
    return ret;
}


If you MUST use this control flow with all this return, one solution could be to use smart pointers:

int MainClass::testFunction() {
  numThreads++;
  std::unique_ptr<char *> a(new char[100]);
  // Some computation with a
  if (condition1) {
    numThreads--;
    return -1;
  } 
  std::unique_ptr<char *> b(new char[100]);
  // Some computation with b
  if (condition2) {
    numThreads--;
    return 42;
  } 
  // Some more stuff. 
  numThreads--;
}

In the worst and weird case, maintaining your current control flow and NOT moving a or b they will be freed on exiting testFunction.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜