25

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.

Community
  • 1
  • 1
user1810087
  • 5,146
  • 1
  • 41
  • 76
  • 3
    I don't see any `std::string`s that are *members* of `exception`. – Joseph Mansfield Apr 05 '13 at 10:01
  • 2
    `std::runtime_error` uses a `std::string`. – Alex Chamberlain Apr 05 '13 at 10:12
  • so using a string as parameter is ok? – user1810087 Apr 05 '13 at 10:20
  • 3
    `std::runtime_error` has a parameter that is a *reference* to a string. Nowhere does it say that it stores one (or creates any copies). – Bo Persson Apr 05 '13 at 11:21
  • @sftrabbit & BoPersson Feel free to add this as an answer and clarify the reason why `std::string` as exception member is evil. – Christian Rau Apr 05 '13 at 12:21
  • 15
    This "rule" comes from an overblown concern that an exception thrown when the program is out of memory will lead to a call to `terminate`, which will, as the name suggests, terminate the application. Yes, that happens. If the program is out of memory, there's usually very little you can do about it, and terminating is usually the right solution. – Pete Becker Apr 05 '13 at 15:13
  • 1) Also consider strict exception guarantees. An exception whose copy ctor is not `noexcept` is a bad idea in general. 2) The original exception might be unrelated to a `bad_alloc`. – dyp Apr 05 '13 at 17:56
  • 1
    @DyP, with all respect, if you don't have enough memory to allocate even a 1Kb string in response to an exception, it probably is related to a `bad_alloc` or another resource issue. – Nathan Ernst Apr 06 '13 at 01:08
  • @NathanErnst I agree, but no one prevents you from including a text-based adventure in your error string (or a detailed description). The point I want to make is that it's error prone, not that it'll probable to lead to `bad_alloc`s. You'll most likely need to compose that error string, too. – dyp Apr 06 '13 at 13:55
  • @Null Thank you for correting my bad english... :) – user1810087 Nov 12 '15 at 12:35

2 Answers2

19

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.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226
  • "you can safely use a std:string in your exception type" What about constructing (composing) the initial `std::string`? This might as well result in an exception (it's as probable to raise a `bad_alloc` as any copy operation of the string IMHO). – dyp Apr 06 '13 at 17:48
  • 3
    @DyP: Creating the initial object that will be thrown is not part of actually throwing the exception, so if it causes a `bad_alloc` it won't cause a double exception immeditate `terminate()` – Chris Dodd Apr 06 '13 at 18:17
  • True, but I think it's a reason for not using `std::string` as exception non-static (non-ref, non-ptr) data members (because it has to be created / composed). – dyp Apr 06 '13 at 18:38
  • This answer appears to get its points about the move constructor wrong. An exception object is generally NOT move constructed and even in C++11 you will run into trouble if the copy constructor fails during the throwing of an exception. – Kenji Jul 11 '16 at 22:30
  • @Kenji: move constructors will be used (if available) when catching an exception by value (rather than by reference) -- catching an exception by value will always involve a move or copy of the exception value from the temporary created in the throw to the named local of the handler (see 15.1.3 in the spec). If this copy or move throws an exception, `terminate` will be called. If you always catch exceptions by reference, then this is not an issue. – Chris Dodd Jul 12 '16 at 00:10
  • "move constructors will be used (if available) when catching an exception by value (rather than by reference) -- catching an exception by value will always involve a move or copy of the exception value from the temporary created in the throw to the named local of the handler (see 15.1.3 in the spec)." I'm sorry, I tried that on GCC 4.8.1 and it did not call the move constructor, it called the copy constructor. Yes, I supplied the correct flag, `-std=c++11`. Maybe the standard says this but I have been unable to move-construct _any_ exception from `throw` so far. – Kenji Jul 12 '16 at 10:39
  • Update: Move-construction gets called if you catch the exception by reference. But if you catch it by value, I have been unable to make it use the move-constructor. This is kinda finicky to rely on. So, if someone catches without & then your program is prone to terminate() calls if the copy constructor may fail. I don't like that. Of course, you could make the copy constructor private, that'd work. – Kenji Jul 12 '16 at 10:45
5

While I think not using std::string for core, fundamental exceptions may be a good guideline, I don't think user-facing libraries/applications should necessarily follow this.

There may be other reasons, but you hit on the primary one: you'd like to indicate to the user (or developer) contextually meaningful information, which you often cannot do with a mere literal string. A dynamic allocation must occur in order to do this. If, for some reason, you had a bad_alloc, you're probably already hosed to begin with, so it doesn't buy/lose you anything.

Edit:

By the way: the destructor of std::exception is marked as virtual for a reason!

Nathan Ernst
  • 4,540
  • 25
  • 38
  • Why has this contextually meaningful information to be stored in a string? Why not use specifically derived exception types with specific data members (it might be necessary to use strings _here_)? Composing the string while creating the exception is futile if noone uses it, so why not create it on-demand? – dyp Apr 06 '13 at 14:00
  • 1
    I'm not saying it's necessary; I'm saying it may be useful. Also, all exceptions will be effectively dynamically allocated, anyways. (Just think, how does `std::exception const&` become `MyException const&`?). Dynamic allocations for exceptions should be avoided, but I wouldn't say it's a no-no. The point was, if you don't have enough memory to allocate a string to provide a meaningful error, you've probably got issues elsewhere. – Nathan Ernst Apr 09 '13 at 01:40
  • "also, all exceptions will be effectively dynamically allocated, anyways." that is to say - otherwise the runtime must know statically at link time the size of all possible exceptions which may occur. And there are certainly circumstances on which it may not know. – Nathan Ernst Apr 09 '13 at 01:42