Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

(not) using std::string in exceptions

Tags:

I'm always reading that I should not to throw a std::string or some other classes allocating memory. like here or more importantly here on point 3. - Don't embed a std::string object.

So now I'm trying to insert boost::exception to my project and what do I see: lots of strings.

Why doesn't boost comply with its own recommendation?

And if I have parameters which can't be hardcoded, like safed in an config-file, how can I put them into an exception, without using std::string?

Or is the guideline don't use std::string only a do use std::string as seldom as possible guideline? I'm a bit confused...

I've done some research. Please correct me if i'm wrong.


If I understand it right, it's all about the allocation during the throw and what is happening to the allocated memory. So the memory gets lost if I allocate it in the constructor and it can't be freed in the destructor of the exception, that will produce a memory-leak. But it's okay to allocate this before throwing, so the exception is clean.

I tried this:

struct xexception {   int *ttt[10];   xexception() {     ttt[0] = new int[0xfffffffL];     ttt[1] = new int[0xfffffffL];     ttt[2] = new int[0xfffffffL];     ttt[3] = new int[0xfffffffL];     ttt[4] = new int[0xfffffffL];     ttt[5] = new int[0xfffffffL];     ttt[6] = new int[0xfffffffL];     ttt[7] = new int[0xfffffffL];     ttt[8] = new int[0xfffffffL];     ttt[9] = new int[0xfffffffL];   }    ~xexception() throw() {     //never happen     delete[] ttt[0];     delete[] ttt[1];     delete[] ttt[2];     delete[] ttt[3];     delete[] ttt[4];     delete[] ttt[5];     delete[] ttt[6];     delete[] ttt[7];     delete[] ttt[8];     delete[] ttt[9];   } };  int main(int argc, const char *argv[]) {   try {     throw(xexception());   }   catch (const xexception &e) {     std::cerr << "\nttt " << e.ttt[0][0] << std::endl;   }   catch (std::bad_alloc) {     std::cerr << "bad alloc" << std::endl;   }    return 0; } 

The result is, I get the bad_alloc and a huge memory leak.

Now if I do the allocation before, it also throws the bad_alloc but before the exception is created.


My exception to the exception concept is:

Who cares? If I have a bad_alloc in my program, because of a memory_leak or something else (I'm talking about programs on PCs not microcontrollers) I have other problems. Maybe I can figure out that a bad_alloc happened, but where? On my alloc during a function (one of maybe 1000) or in the std::string (well I know it's the string but ... no possibility to manipulate the memory of the string... or its to dissipated).

try {   // where is the error???   int *x = new int[100];  // here?   ....   int *y = new int[100];  // or here?   ....   int *z = new int[100];   ....   int *w = new int[100];   ....   int *t = new int[100];   ....   int *f = new int[100];    ....    std::string str("asdfasdfasdfasdfasdfasdfasdf"); // maybe here } catch (the error) {   .... } 

And then? Shall I try to figure out where it's happening? Therefore I would use valgrind not exceptions.

void foo() {   int *i = new int[1];   foo(); }  try {   foo(); } chatch( bad_boy ) {   go_exception_handler_go(parameters); // oh, shit happens: also an stack_overflow may happend, cause stack is also full } 

Or shall i manipulate the errormessage and log it, what definitively would throw the next bad_alloc.

Please don't misunderstand me. Since I've seen the boost::exception I've rewritten my exception class (till waiting on an answer) but I also think it is not really necessary to pick up every grain of sand.

like image 897
user1810087 Avatar asked Apr 05 '13 09:04

user1810087


People also ask

Why do we use std::string?

std::string class in C++ C++ has in its definition a way to represent a sequence of characters as an object of the class. This class is called std:: string. String class stores the characters as a sequence of bytes with the functionality of allowing access to the single-byte character.

Why string is not declared in this scope C++?

string is in the std namespace. You have the following options: Write using namespace std; after the include and enable all the std names: then you can write only string on your program. Write using std::string after the include to enable std::string : then you can write only string on your program.

Does std::string allocate?

The object str (it is the instance of the class std::string ) is allocated in the stack. However, the string data itself MAY BE allocated in the heap. It means the object has an internal pointer to a buffer that contains the actual string.

Is std::string contiguous?

The std::string class manages the underlying storage for you, storing your strings in a contiguous manner. You can get access to this underlying buffer using the c_str() member function, which will return a pointer to null-terminated char array.


1 Answers

The advice is basically telling you "Don't use any construct that might throw an exception in an exception". That's because if you get an exception while trying to throw an exception, the C++ runtime will just immediately call terminate() and kill your program.

Now if (either) of the exceptions involved would just call terminate() anyways (as is the default for an uncaught exception), then you don't really need to worry about it. For example, if your application can't handle bad_alloc (can't recover from out-of-memory), then you don't need to worry about copy constructors (such as std::string) that might throw it.

But if you want to be able to catch and recover from a bad_alloc, you need to ensure that none of your exception copy constructors can cause one. If you're writing a library that other applications will use, you should not assume that the application does not want to handle bad_alloc.

C++11 make this much easier by using move constructors (instead of copy constructors) where possible. Since the move constructor for std::string never throws exceptions you can safely use a std:string in your exception type as long as you properly implement move constructors, and ensure that they are used. Note that the initial construction of the object to be thrown in a throw expression is NOT part of the exception throwing process, so that constructor can throw an exception without causing a double exception (and terminate()). So if you have:

throw some_function(); 

some_function might throw an exception (such as bad_alloc) without returning an object to be thrown and that's fine. If it doesn't throw an exception (and returns a valid object), the move constructor for the exception type will be used (if available) for the exception throwing process, and that move constructor must not throw an exception.


Completely independent of the above, whenever you call new you need to ensure that exactly one spot will call delete in every possible case, or you'll leak memory (or crash from a double delete). This becomes tricky any time you have a function that calls new and then does something else that might throw an exception (such as call new again). If this happens in a constructor, the destructor for the object will not be called (though destructors for base classes and fields will be), so you can't do the cleanup in the destructor as you are trying to do with your example.

Fortunately std::unique_ptr exists to make this much easier. If you write your exception class as:

struct xexception {   std::unique_ptr<int[]> ttt[10];   xexception() {     ttt[0].reset(new int[0xfffffffL]);     ttt[1].reset(new int[0xfffffffL]);     ttt[2].reset(new int[0xfffffffL]);     ttt[3].reset(new int[0xfffffffL]);     ttt[4].reset(new int[0xfffffffL]);     ttt[5].reset(new int[0xfffffffL]);     ttt[6].reset(new int[0xfffffffL]);     ttt[7].reset(new int[0xfffffffL]);     ttt[8].reset(new int[0xfffffffL]);     ttt[9].reset(new int[0xfffffffL]);   } }; 

it should work and not leak memory.

like image 118
Chris Dodd Avatar answered Oct 10 '22 01:10

Chris Dodd