News for hackers. Stuff that matters.
Re:Memory management in C programs (2014) by deathanatos on Monday July 18, 2016 @09:58PM (#12119065)
> plenty of modern code written in languages like C++ (whose exception support is often eschewed) and Go gets by without [exceptions].

I would not consider C++ code that eschews exceptions "modern". To eschew exceptions almost always entails giving up RAII, and if you give up RAII you've done yourself a huge disservice: you're now stuck manually managing memory and resources, and suffering all the bugs that come with that. While RAII does not outright prevent these bugs (which is one of the reasons I'm a huge fan of Rust), coding along its principles greatly reduces the risk that you will run into problems.

If you're wondering: giving up exceptions means a constructor has no way to signal failure, as the result of a ctor in C++ is always either an exception, or a constructed object. Most "no exceptions" C++ code I've seen opts to construct what I call "zombie objects"; internally, the object tracks that an error has occurred, and all uses of the real object must be first checked against that internal error flag to ensure the object isn't a zombie (or if it is, then error). The object is effectively a null pointer, and has all the trouble that entails.

(I also won't pretend that exceptions aren't without problems; in particular, the argument against them because you can't tell, at a particular point in a function's code, if an exception can occur, or where it would be caught, is completely valid, but no different in many popular languages, such as Python, C#, and in some ways, Java. However, I think the advantages of RAII — and exceptions — outweigh their disadvantages.)

> It just has to be written so that most functions manually propagate error codes they receive

When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore.

Re:Memory management in C programs (2014) by flohofwoe on Tuesday July 19, 2016 @02:30AM (#12120179)
RAII (or rather the only useful part of the idea: that the destructor is called when an object goes out of scope) without exceptions is totally fine if you're not too dogmatic about initialising everything (especially not things that can go wrong) in the constructor but instead have one or more separate init methods with a return value. The only important part is to cleanup everything in the destructor. This is all IMHO of course, but I've been writing C++ code for 20 years just fine without ever thinking 'gee it would be nice to use exceptions here'.
Re:Memory management in C programs (2014) by yoklov on Tuesday July 19, 2016 @02:54AM (#12120245)
Strongly agree. RAII without exceptions is fine. Exceptions are deeply flawed for code understandability, and anything you get by doing work that can fail in the ctor is mot worth it.
Re:Memory management in C programs (2014) by lmm on Tuesday July 19, 2016 @07:10AM (#12121032)
Being able to construct without initialising is the flipside of being able to descope without deinitialising, and introduces much the same problems. It makes it much harder to use immutability (admittedly something that's hard in C++ in any case). It makes sharing objects between threads much harder to reason about, as you need to make sure init a) only happens once, but more importantly b) the effects are visible to any other thread before it uses the object.

How much code have you written with exceptions? A lot of features you don't miss if you don't use them, but once you're used to use them it's very frustrating to live without them. Back when I wrote C I never thought "gee it would be nice to use an anonymous function here", but now that I'm used to them it's very frustrating to not have them.

Re:Memory management in C programs (2014) by flohofwoe on Tuesday July 19, 2016 @10:16AM (#12121887)
Admittedly I haven't written much code with exceptions, only when using C++ frameworks that heavily rely on them, but this was always for tools where productivity was more important than performance, my actual background is game development.

I have stumbled more recently over another case where the constructor/destructor paradigm in C++ is not ideal, since in some cases it 'encourages' constructing objects on the heap, just for the reason that allocation and initialisation are the same operation, I haven't thought this completely through yet, but bear with me:

Basically I'm trying to write code in a way that minimizes heap allocations. In the extreme case I have just one big 'application object', and all other objects are embedded in this application object. For variable number of objects, pools/arrays with the max capacity are embedded. If taken to the extreme, this model only does a single allocation at application startup (or it could even live on the stack).

However, there are systems which need to be initialized later, and in a specific order (for instance rendering, audio, input, etc...). The objects for these systems have already been created and had their constructor run, but they are not ready for use. That's where I need separate initialisation steps, unless I want to put those objects back on the heap. And in reverse, I need to teardown graphics, audio, input before the big application object is destroyed (and the destructors of the embedded objects are called).

Now I don't think that this extreme case of trying to minimize dynamic allocations down to 1 is particularly useful in real world code, it's more of a though experiment, but in this case, the good ol'e C way of separating allocation/deallocation from initialization/teardown is actually more useful.

Unfortunately I have stumbled over C++ frameworks which basically require to create all objects on the heap, which in my opinion is a design fault. A framework should not dictate to its users whether objects are created on the stack, on the heap, or are embedded in other objects.

Re:Memory management in C programs (2014) by lmm on Wednesday July 20, 2016 @06:46AM (#12128037)
I agree that developers and code can have a lot of knowledge that lets them make allocations a lot more efficiently than the likes of glibc's malloc() (which have to handle a lot of edge cases and a lot of different workloads). As an extreme example, if you can architect around cooperating short-lived processes (Erlang-style) you can use a simple bump allocator and "dynamic" allocation is effectively (almost) free.

I wouldn't want to be doing anything custom around allocation without the support of a type system though. Having allocated-but-not-fully-initialized objects hanging around and looking exactly like fully-initialized objects is a recipe for disaster, and while it may be possible to keep track of things yourself through constant vigilance, the cognitive load just isn't worth it IMO. So if I had to do that kind of thing in C++ I'd probably look at having a custom allocator and some way to pass hints to it, rather than doing allocation and initialization separately in "normal" code. (Well, in reality I wouldn't use C++)

> Unfortunately I have stumbled over C++ frameworks which basically require to create all objects on the heap, which in my opinion is a design fault. A framework should not dictate to its users whether objects are created on the stack, on the heap, or are embedded in other objects.

Depends on the use case IMO. The flexibility of being able to control all your objects is valuable, but it doesn't come for free. One can't really generalize about what's idiomatic C++, because there's such a broad range of users.

Re:Memory management in C programs (2014) by richardwhiuk on Tuesday July 19, 2016 @10:07AM (#12121839)
You can perfectly well have classes which have different levels of initialization and clean them all up in the destructor...

RAII is generally useful without exceptions.

Re:Memory management in C programs (2014) by kabdib on Monday July 18, 2016 @11:01PM (#12119271)
I've not seen a large production C++ code base that did anything with exceptions other than to catch them, record some debug information and restart.

This is especially true when third party code is involved. C++ is hard enough to get right without exceptions; dealing with failure in bodies of code that you didn't write and maybe don't have control over (or even source code of) is death on wheels.

If that means the code I'm working on isn't modern . . . I think that's fine. But the first volume (of three?) of Herb Sutter's Exceptional C++ should be enough to convince anyone that dealing with exceptions "correctly" is time better spent eschewing exceptions and restructuring your code so you can do worthwhile hard things.

Re:Memory management in C programs (2014) by Too on Tuesday July 19, 2016 @01:08AM (#12119843)
If you have a server that should process 100000 requests and processing one of them happen to throw an exception, it's very easy to just wrap request->process() with try catch and continue processing the rest of the requests.

You can also use it to recover parts of the processing with sane defaults. String parse integer exception? Use constant 0 instead if that makes sense, etc etc.

I don't see why people make exceptions in c++ into a big deal and a big mystery, in other languages people just use them, the only caveat in c++ is throwing in destructors but that's easy to remember. In one place I worked they had meetings for over a week to discuss whether it was ok or not to throw exceptions in a constructor or not T_T.

Re:Memory management in C programs (2014) by zvrba on Tuesday July 19, 2016 @02:01AM (#12120054)
A colleague of mine is working on a C++ Qt application which can encounter failures in many different ways. It is not feasible to restart the application, the user must get a proper error message instead. He didn't use exceptions because Qt doesn't use them (it returns "empty" objects instead) and.. well, error-handling without exceptions is brittle.

In hindsight, he should have coded the logic/business part in "modern" C++ with exceptions and implemented thin adapters to the GUI part. That's what I did in another piece of code, and am extremely happy with the outcome.

It's not even hard to get them right, "the lightbulb" lit up for me after reading the chapter on exceptions in Stroustrup's TC++PL.

Re:Memory management in C programs (2014) by perspectivep on Tuesday July 19, 2016 @12:31AM (#12119676)
They make it easier to diagnose crashes by providing more context than just an error code without extra effort (like a call stack). Fail fast is another option but that takes control away from the caller.
Re:Memory management in C programs (2014) by deathanatos on Monday July 18, 2016 @11:44PM (#12119443)
> C++ is hard enough to get right without exceptions

In what manner? If you use exceptions, you must (IMO) use RAII, or yes, it will be painful. Every example of "painful C++" I've seen involved someone ignoring that, and that is fighting the language. I understand that's a bit of a strawman argument, but you didn't elaborate enough in your post to debate it.

If you are using RAII, exceptions shouldn't be terribly hard to "get right"; an exception propagating up the stack will free resources as it goes. (This is the same behavior, again, as numerous other languages; except in C++, I have the benefit of RAII, in others, I generally do not.)

> dealing with failure in bodies of code that you didn't write and maybe don't have control over (or even source code of) is death on wheels.

It's peculiar that I only ever hear this argument against C++, when Python, C#, and to an extent Java¹ work in the same manner. Were you writing a C++ webserver, for example, I expect that you would do exactly what my current job's Python webserver does: if an uncaught exception makes it to the main request handler, it is caught in a catch-all, duly logged, and a 500 served in response. This is appropriate both in Python and C++ IMO. (Whereas in the hypothetical C++ side, I gain the benefits of static typing and RAII that I can never have in Python.)

In my day-to-day Python, we quite often learn about uncaught exceptions — from third-party code that I didn't write and maybe don't have control over — in production; sometimes, we will add a handler for that particular exception where appropriate (it was a bug) or fix the underlying symptom (the exception indicates a larger problem, and allowing it to propagate to a good catch-all point was appropriate, as was 500'ing the request).

But again, "death on wheels" is not really something I can provide worthwhile argument against. Perhaps where third-party code has caused pain (at least, for me) is when a very low level (say, socket error) propagates through the stack; there might not be a good way to catch these beyond "catch everything", but again, that's not unique to C++. The biggest problem I've had with these is that they lack context: what caused this socket error? Or, if some library catches & re-throws its own exception class, too often do they discard the inner error, and I lack the root cause (a different lack of context). But you see this in Python as well. Error codes are perhaps the worst, as they will quite often force you to lose context; do you expose error codes for all of your inner error conditions? (And theirs, transitively?). (I've never seen a non-exception C++ or a C codebase use anything aside from error codes as a substitute.)

Re:Memory management in C programs (2014) by zvrba on Tuesday July 19, 2016 @02:07AM (#12120069)
>> C++ is hard enough to get right without exceptions > In what manner?

I guess he's referring to noexcept/basic/strong exception guarantees. Yes, writing code with strong guarantees is hard, but what makes it hard is that you have to write your code "transactionally", i.e., either 1) prepare changes and "commit" them in one go, or 2) roll back changes if an exception occurs.

But writing code with "strong guarantee" is just as hard, if not harder, with error codes.

Re:Memory management in C programs (2014) by yoklov on Tuesday July 19, 2016 @02:58AM (#12120255)
Its not harder. You know exactly where that function can return, with exceptions its very hard to know what might throw in practice.
Re:Memory management in C programs (2014) by zvrba on Tuesday July 19, 2016 @09:51AM (#12121734)
With exceptions you can use something like scoped exit (http://www.boost.org/doc/libs/1_61_0/libs/scope_exit/doc/htm...) for automatic rollback and neat cleanup even in multilevel "transactions". With error codes, nothing happens automatically, hence it's easy to mess up cleanup/rollback.

Yes, you can still use RAII for rollback/cleanup in combination with error codes, but then you get the worst of both worlds: the (minor) complication of writing RAII classes AND code cluttered with manual error-checking.

Re:Memory management in C programs (2014) by blub on Tuesday July 19, 2016 @12:19AM (#12119617)
I've seen exception classes split into recoverable and non-recoverable. The former clean up as they propagate and move the application to an initial known state in a state machine.

This works fine. I think the worst bug was an exception thrown from the initial state which lead to an infinite loop.

Re:Memory management in C programs (2014) by cmrdporcupine on Monday July 18, 2016 @11:24PM (#12119360)
I don't think it's a secret that Google's C++ code base (very large and growing) does not use exceptions. And since starting there I have not missed them. Exceptions make execution flow hard to reason about. And RAII is most certainly still a thing without exceptions. There's lots of other ways to unwind the stack.
Re:Memory management in C programs (2014) by Ace17 on Tuesday July 19, 2016 @01:26AM (#12119914)
Google is an example that writing production code that doesn't use exceptions is possible, not that this is the right thing to do.

They said that their decision was primarily legacy-motivated (in short, they were fearing that introducing exceptions in a non-RAII codebase would lead us to a long period of instability).

I'll add that in some situations (e.g when freeing resources) the control flow gets very convoluted, and you don't want to deal with directly. See: Andrei Alexandrescu - Declarative Control Flow ( https://www.youtube.com/watch?v=WjTrfoiB0MQ )

Re:Memory management in C programs (2014) by niccaluim on Tuesday July 19, 2016 @01:56AM (#12120038)
>> It just has to be written so that most functions manually propagate error codes they receive

> When I've had to write C, I've found this to be exceptionally tedious to do correctly, and all too easy to ignore.

Indeed. I write Go all day and I type "if err != nil { return nil, err }" so often I could scream.

Re:Memory management in C programs (2014) by pjc50 on Tuesday July 19, 2016 @05:31AM (#12120770)
They should probably add a keyword for that phrase.
Re:Memory management in C programs (2014) by nshepperd on Tuesday July 19, 2016 @03:14AM (#12120291)
Eh? RAII works perfectly fine without exceptions. Either - You write a constructor that cannot fail for RAII types that cannot fail, or - return some kind of option type from your BuildRAII() function, and handle the failure case however you want.
Re:Memory management in C programs (2014) by ori_b on Monday July 18, 2016 @10:26PM (#12119156)
> To eschew exceptions almost always entails giving up RAII

How so? RAII works just fine when you return errors.

Re:Memory management in C programs (2014) by robotresearcher on Monday July 18, 2016 @10:50PM (#12119239)
How about in constructors?
Re:Memory management in C programs (2014) by telotortium on Monday July 18, 2016 @11:12PM (#12119318)
If a constructor can fail, don't make that constructor public. Instead, add a trivial constructor to allocate an empty instance of the object, then make your possibly-failing constructor a factory function that takes whatever arguments you would pass to the constructor and returns an error code. The object being "constructed" should then be passed by reference as one of the arguments to the factory function and filled in. (If you're writing this in Rust, you have better options, since you can explicitly take ownership of some of the factory function arguments and avoid the need to allocate a trivial instance of the object by returning it as one of the variants of the sum type Result<T, E>.)

Note: I don't actually know C++ very well. It's possible there's a more idiomatic way to accomplish this. In particular, another wrapper class could be created that owns an instance of the trivially-constructed instance mentioned above. The factory function could then take an instance of the wrapper class and then modify and return an instance of the class you actually want to obtain.

Re:Memory management in C programs (2014) by robotresearcher on Monday July 18, 2016 @11:58PM (#12119512)
Constructors are resource allocation, so they can always fail.
Re:Memory management in C programs (2014) by PeCaN on Tuesday July 19, 2016 @12:10AM (#12119572)
You can't really tell if the stack overflowed in a constructor though, plus that will essentially /never/ happen, so I think it's okay to exclude stack overflows when creating stack allocated objects as failure conditions. In that case no, a lot of constructors won't be able to fail.
Re:Memory management in C programs (2014) by comex on Tuesday July 19, 2016 @12:16AM (#12119601)
Any function can overflow the stack. If the object is constructed on the heap, 'new' can fail but will always throw an exception; if you aren't using exceptions it will just crash the process.
Re:Memory management in C programs (2014) by PeCaN on Tuesday July 19, 2016 @12:35AM (#12119690)
Yeah. That's why I don't say it's a failure condition for a stack-allocated object to overflow the stack. Notice my parent comment said:

> Constructors are resource allocation, so they can always fail.

Which is false.

Re:Memory management in C programs (2014) by robotresearcher on Tuesday July 19, 2016 @01:45PM (#12123359)
Can you explain why this is false? If I run out of stack or heap by exceeding the process limit, the constructor must fail. Is this incorrect?

I see your comment above that stack overflows essentially never happen, but having watched it happen, I disagree.

Re:Memory management in C programs (2014) by ori_b on Tuesday July 19, 2016 @03:22PM (#12124129)
This is not an error that you can reasonably recover from. If you are out of stack, even constructing an exception to throw is likely to fail.

Because the only correct behavior here is to terminate the program immediately, it doesn't matter in the discussion of whether to use exceptions or not.

Re:Memory management in C programs (2014) by lmm on Tuesday July 19, 2016 @07:13AM (#12121039)
new can and does fail when you're out of memory, and this is a condition that many programs do need to handle (for some programs crashing is appropriate and in that case sure, you can live without exceptions). In a pedantic technical sense that's not the constructor failing, but your options for handling it are much the same.
Re:Memory management in C programs (2014) by comex on Tuesday July 19, 2016 @12:14AM (#12119592)
std::optional is now confirmed for C++17, which can make the API a bit nicer. Using factory functions as pseudo-constructors also lets you name them, which is often nicer than having to rely on argument types to disambiguate constructors with different purposes.
Re:Memory management in C programs (2014) by reverius42 on Monday July 18, 2016 @11:01PM (#12119270)
Init methods is the answer to that (but it potentially forces some additional complexity around initialized/not-initialized state into the other methods).
Re:Memory management in C programs (2014) by deathanatos on Monday July 18, 2016 @11:54PM (#12119484)
As I stated in my original post, these have the same classes of problems as null pointers: you have "constructed" an object whose internal state in invalid; a instance of Foo is either a Foo that init'd successfully or not-a-Foo (that failed to init/isn't initialized; a "zombie object") just as a pointer is either a pointer-to-something or a pointer-to-nothing. This is why you say "it potentially forces some additional complexity around initialized/not-initialized state into the other methods.

At least in C++ (and many other OO languages), I find the downsides of having these Jekyll and Hyde classes outweigh any benefits that may come of ignoring exceptions.

Re:Memory management in C programs (2014) by p4wnc6 on Monday July 18, 2016 @11:16PM (#12119334)
In fact, I think managing this extra state, also dealing with complications that could arise from object slicing, were the major reasons given when Martin Sústrik argued he should have originally started out with ZeroMQ in C and not in C++.

< http://250bpm.com/blog:4 >

Re:Memory management in C programs (2014) by duaneb on Tuesday July 19, 2016 @02:12AM (#12120088)
How does RAII deal with reference loops? It is trivial to create a loop of owned pointers in memory that never decrement, and it isn't that unusual of a pattern not to be a concern.

Just curious; I used a combination of strong/weak reference counting when I dealt with this. It wasn't fast, but it did appear to be less prone to implicit leaking.

Re:Memory management in C programs (2014) by lukewink on Tuesday July 19, 2016 @02:23AM (#12120139)
One method is using weak_ptr.