kejran
kejran

Reputation: 13

How to safely deal with an array pointer when throwing an exception

When trying to encapsulate C API in C++ method, i found a problem in throwing an exception:

int status;
char* log = nullptr;
int infoLogLength;

getFooStatus(&status);
getFooLogLength(&infoLogLength);

if (!status) {
  log = new char[infoLogLength];
  getFooLog(infoLogLength, log);
  throw std::runtime_error(log);
}

I am not allowed to modify the interface methods in any way.

From what i understand, i am required to reserve memory for the method to fill, and operate on that. However, throwing the exception will return from the method, not letting me to free the resources. Is my code correct, or should i go around this in some other way?

Upvotes: 1

Views: 116

Answers (3)

Remy Lebeau
Remy Lebeau

Reputation: 597550

std:runtime_error expects a std::string, so give it a std::string instead of a char*:

int status;
getFooStatus(&status);

if (!status) {
    int infoLogLength;
    getFooLogLength(&infoLogLength);
    std::string log(infoLogLength, '\0');
    getFooLog(infoLogLength, &log[0]);
    throw std::runtime_error(log);
}

Alternatively, you can pass a char*, simply allocate it in a way that promoted auto-freeing, eg:

int status;
getFooStatus(&status);

if (!status) {
    int infoLogLength;
    getFooLogLength(&infoLogLength);
    std::vector<char> log(infoLogLength);
    getFooLog(infoLogLength, &log[0]);
    throw std::runtime_error(&log[0]);
}

Upvotes: 2

Kaz
Kaz

Reputation: 58627

There is a simple way to deal with exceptions, without having to refactor the code so that the array is managed by an object whose destructor cleans it up. Namely:

char* log = nullptr;

try {
  int status;
  int infoLogLength;

  getFooStatus(&status);
  getFooLogLength(&infoLogLength);

  if (!status) {
    log = new char[infoLogLength];
    getFooLog(infoLogLength, log);
    throw std::runtime_error(log);
  }
} catch (...) {  // special C++ syntax: "catch any exception"
  delete [] log;
  throw; // special C++ syntax: "re-throw" current exception
}

If all you have is a single catch (...), it looks a lot as if C++ supports finally. The difference betwen catch (...) and a finally feature is that catch (...) does not execute unconditionally: it only executes if there isn't a more specific catch. So, if you were to add additional catch clauses in the same try block, they would all have to repeat the delete [] log clean-up action.

This could be mitigated by using nesting:

try {    // real exception-handling try
  try {   // try for unwind protection only
    ...
  } catch (...) {
    // clean-up statements
    throw;
  }
} catch (actual_error &err) {
  ...
} catch (another_exc_type &exc) {
  ...
}

Upvotes: 0

David Zech
David Zech

Reputation: 755

As far as I know runtime_error has two overloads, one with a const *char and another as const string&.

I believe constructing a std::string as a local variable and passing that to runtime_error should result in it being cleaned up properly.

Upvotes: 0

Related Questions