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 new
s at different points. At every return point, the set of delete
s 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.
精彩评论