pouët.net

Go to bottom

Writing C++ efficiently

category: code [glöplog]
doomdoom, i liked that article from the 0MQ guy. given that for most practical purposes, C++ is a superset of C, it was really about design and not about dissing C++. he said "I should've not used C++'s specific features, and instead designed my software differently". Why is that "full of fail"? (genuinely curious)

also, what's wrong about the idea of only using templates in C++, and not its other features?
added on the 2012-05-11 15:32:53 by skrebbel skrebbel
eh, its other non-C features, i meant.
added on the 2012-05-11 15:33:12 by skrebbel skrebbel
skrebbie: I don't think he understands it that way. If you look at his comment down on page 1, he complains that there is no such thing as "C with templates". Even though he's been using "C with templates" for five years on this very project. So I think he's making the same strange assumption that many people make, which is that you can't write C++ without writing code "the C++ way", whatever that is supposed to mean. Except there is no "C++ way". It is what you make of it.

But it's not just that. He doesn't sound like he ever really bothered to understand these C++ features that have caused him so much grief. He reasons, for example, that without exceptions he has no choice but to split object creation into an allocation and an initialisation phase, leaving him with an undesirable intermediate state where an object exists but is not yet valid. That's completely wrong, of course. All he needs is a static object creation method per class which promises to return either null or a fully initialised object. That also neatly solves his problem with inheritance:

Code:class Bar { public: static Bar* Create() { Bar *bar = new Bar(); if (!bar->init_Bar()) { delete bar; return null; } return bar; } private: Bar() { } bool init_Bar() { ... return true or false, cleanup own mess } } class Foo : public Bar { public: static Foo* Create() { Foo *foo = new Foo(); if (!foo->init_Bar() || !foo->init_Foo()) { delete foo; return null; } return foo; } private: Foo() { } bool init_Foo() { ... return true or false, cleanup own mess } }


There. If he wants a Bar, he calls Bar::Create(). If he wants a Foo, he calls Foo::Create(). If either call fails, it returns null and there are no side effects. There is no need to burden the calling function with an intermediate "semi-initialised" state. Encapsulation!

But either way, why not just let your constructor throw an exception? With proper use of RAII this is by far the most convenient thing to do. He doesn't like exceptions, though, because he does not understand them:

Quote:
Consider the case when the exception is not handled in the function that raises it. In such case the handling of the error can happen anywhere, depending on where the function is called from.


You might say: consider the case where an error code is returned from a function. In such a case the handling of the error can happen anywhere! He doesn't get that exeptions do exactly what error codes do, except in a less error-prone, less cluttered, more flexible and more maintainable way.

It's rare that a function will handle an exceptional case without ultimately returning an error code to its caller. If you try to open a file and it's not there, what's the OpenFile() function supposed to do with that realisation? Try harder? No. It exists to open files. If there is no file to open, then OpenFile() has no work to perform. All it can do, then, is tell the caller that the file does not exist. And the caller in turn will most likely just propagate that error message up the call stack until eventually the programmer has provided an error handling mechanism, i.e. a place where some action can be taken to remedy the situation, such as asking the user for another filename. And that's what exceptions do, but without all the

Code:"if (errorcode != OK) return errorcode;"


all over the place, and without the risk that you might miss a return value check (resulting in precisely the kind of undefined behaviour he is so afraid of). And they do this all while conveniently freeing up the return value for use by program logic rather than just error handling.

Moreover his worries that exception-related boilerplate code grows exponentially are misguided, to say the least. Yes, when you add a new exceptional condition to consider you might want to add an exception class, but return codes don't free you from that complication at all. At least with exceptions you can create a hierarchy. Like this, for an OpenFile() function:

Code:- Failed to open file - File not found - Access violation - File is read protected - File is locked by other reader - Read error - Media not present - Bad sector


Now supposing you wanted to extend the functionality of OpenFile() and add a "Bad filename" exception, you could simply make that a specialisation of "File not found", and any code that knows how to deal with "File not found" will react as if the file wasn't found. As long as a "Bad filename" exception is-a "File not found" exception, there isn't even a semantic difficulty there. With error codes, however, if you change the possible return values of OpenFile() you would need to check every place that OpenFile() is called to make sure that the new return value results in the same behaviour as "File not found".

So with that in mind it seems like he has it backwards. Exceptions don't come with as much maintenance as error codes, and they make it less likely that errors go unhandled. If you use them correctly, that is. He doesn't. Just look at this:

Quote:
Code:try { ... int rc = fx (); if (rc != 0) throw std::exception ("Error!"); ... catch (std::exception &e) { handle_exception (); }


The whole point of exceptions is that you do not need return codes! If fx() fails, then it should throw its own exception. And you do not need to catch it except where you can take meaningful action to remedy the error - which by the way makes these five-line examples somewhat misleading. You need to look at a few nested functions of at least a couple dozen lines that actually do something before it becomes apparent how much boilerplate you're actually saving.

Anyway enough ranting. :P
added on the 2012-05-11 16:59:27 by doomdoom doomdoom
Code:// todo: best code ever
added on the 2012-05-11 17:00:51 by maali maali
The most efficient way of writing C++ code is having someone else do it.

Unfortunately, doing things the most efficient way is often the most boring too.
added on the 2012-05-11 17:06:55 by trc_wm trc_wm
doomdoom I was going to mention RAII, but you already pointed it out. Finally taking the time out to read about it made me a better programmer in 10 minutes. Can't believe how I used to write working code without it looking back.

Although it would be great if there was a way to specify to the compiler that a method will throw an exception - much like checked exceptions in Java. I think thats a really neat idea, especially when you're churning out code and you dont have time to think too much about the internals of a method you're calling.

Writing a "correct" program (by correct I mean robust, clean and handles all possible cases) for all but trivial examples in C++ seems to be a fucking nightmare quite frankly. Taking a class in functional languages made me so so jealous of how rigorous you can really be with your code. C++ makes everything I do feel dirty and wrong unless I make a huge conscious effort and waste lots of time making it not so.

Some languages make it so that the easiest code to write, is (or is close to) the best code you could write - as in the two go hand in hand, and deviating from best practices requires conscious effort. C++ claims it sacrifices this by essentially giving you no overhead and speed etc. I actually believe just because a language is fast and gives you more control it doesn't mean it needs to sacrifice anything - it's just a crap language, end of discussion.

I swear to God, if no one else used C++ I'd drop it in a second, things have moved on so much. But thats what everyone else uses and what the market seems to want :/

I wish I could claim I was a whizz and be all like "I don't need garbage collection" "I don't need checked exceptions" "I don't need helpful feature x" etc. etc. Fact is though, if you want to write *correct* code, for a relatively large system, you do. Dijsktra wasn't bullshitting when he said "The competent programmer is fully aware of the limited size of his own skull."
added on the 2012-05-11 18:25:45 by xwize xwize
C++ does allow specifying that a method will (or will not) throw an exception :
Code: void translate() throw(unknown_word,bad_grammar) { /* ... */ }


The default behavior is "can throw anything". Use throw() to make it "doesn't throw anything" if you are sure that's the case :)
xwize: Yes, C++ never goes "all the way", but I don't think it can while still remaining a low-level, multi-paradigm language.

As PulkoMandy says you can specify that a function will throw no, any or specific exceptions (at least in Visual C++) but AFAIR the compiler doesn't actually care. E.g. if you mark a function with throw(), and from that function you call another function that's marked with throw(bad_grammar), you'd expect at least a warning, right? But no. :(

Still, if we're comparing to manually propagating integer return codes up the call stack, even C++ exceptions are a huge improvement in terms of writing robust, maintainable code. You can't forget to catch an exception - or you can, but then your process will die and you get a stack trace. For production code it's also easy enough to wrap main() in a try/catch(BaseException) block and produce a detailed error report when something completely unexpected happens.
added on the 2012-05-11 19:54:39 by doomdoom doomdoom
Yes I was aware of C++'s throw statement and yes the compiler doesn't care, makes you wonder why they even bothered.

My original post was exactly about things like this. Maybe there's a way to specify a checked exception using some commenting and having a tool pre-process the code during a build step to find incorrect usage? (Or maybe you don't need to do this and theres an option somwhere, or a tool already available).

Just because the standards comittee are falling over themselves to actually put anything decent in the spec (C++11 is hardly a leap is it, smart pointers?! about time!), it doesn't mean we should take that as a green light to write awful code.

Just look at all the non-standard extensions the popular compilers put in to make up for how crap the language is - anonymous unions are fucking non-standard? So you're telling me that "elegant" vector class I've been using all these years, technically isn't C++. Christ, you'd be mad to actually code in standard C++.

Which brings me back to my original question about efficiency really. What can we do as coders, or what do other people do to get around these and similar problems. How can we constrain or augment the language so that we can write better code faster.

A lot of the discussion so far has been on frameworks and not attempting to code things you might need and just only code the things you do need, which is sound advice, but my original question was more about making C++ less painful.

I suppose the best answer to that is don't use C++ - which is what I was considering, but I wanted to see what others said on the matter first.

added on the 2012-05-11 20:50:08 by xwize xwize
Uh ? Anonymous unions are standard C++, and since C11 they are now standard C as well. So here's at least one good thing about C++; it makes C move forward.

As for exceptions, don't blame the language when the compiler is broken. Make a bugreport ?
Oh shit sorry I mean struct
added on the 2012-05-11 21:05:23 by xwize xwize
Mh... that's less useful :)

Code: struct A { // some fields } struct B: public A { // some more fields }

does the same, right ?
Exception specifications suck (ever dealt with a larger piece of Java code?). Thanks god C# doesn't have them, and in C++11 they're deprecated. Also, http://www.artima.com/intv/handcuffs.html
added on the 2012-05-11 21:24:40 by Moerder Moerder
Quote:

It is sort of these dictatorial API designers telling you how to do your exception handling. They should not be doing that.


Quote:

No, because in a lot of cases, people don't care. They're not going to handle any of these exceptions. There's a bottom level exception handler around their message loop. That handler is just going to bring up a dialog that says what went wrong and continue.


mh...

Quote:

The trouble begins when you start building big systems where you're talking to four or five different subsystems. Each subsystem throws four to ten exceptions. Now, each time you walk up the ladder of aggregation, you have this exponential hierarchy below you of exceptions you have to deal with.


just catch them at a lower level, cleanup what's needed, and throw something more generic ? Encapsulation !
Quote:
just catch them at a lower level, cleanup what's needed, and throw something more generic ? Encapsulation !


Sure. You can do that without exception specs, tho. Other than that, what Heilsberg says. He's definitely right about the dialog bit, and that's why I wholeheartedly disagree whenever somebody comes up with the "error messages don't belong into exceptions" argument.

added on the 2012-05-11 22:03:15 by Moerder Moerder
Here, that's one thing to remember from that article.

Quote:
It is funny how people think that the important thing about exceptions is handling them. That is not the important thing about exceptions. In a well-written application there's a ratio of ten to one, in my opinion, of try finally to try catch. Or in C#, using statements, which are like try finally.


That's exactly it. Exceptions are there to abort program flow and clean up after you. And in C++ you don't even need a finally. You've got destructors where it's well defined when they get executed.
added on the 2012-05-11 22:08:20 by Moerder Moerder
oh yes, exceptions in c++/java are not very comfortable to deal with.

for my own (pet-)projects, I've built me a language that has a feature I call "uncritical" exceptions -- such an exception propagates just to the caller level,
if it is not caught there, it will automatically be discarded.
added on the 2012-05-11 22:13:49 by xyz xyz
Am I the only one who turns off exception handling?
added on the 2012-05-11 22:23:15 by trc_wm trc_wm
I hardly know anyone who actually uses exceptions in c++ :-)
added on the 2012-05-11 22:31:51 by xyz xyz
I do turn exception handling off sometimes, when I don't need it :) (like any C++ feature)

I tend to throw const char* rather than proper exceptions, and yes, I use them for displaying a dialog. But that's not the only use I have for them, and I can throw other types when it's not meant for user knowledge. This does not require writing much code, it leaves an application get the error message to the user in any way that makes sense (dialog, console message, shout it loud with a vocal synth, whatever). In plain C I'd have to return a string to caller, and then forward it with a lot of if ret != NULL) return ret; after each function call. I find that these if checks disrupt the code flow of what I'm trying to work on, make me care about stuff I shouldn't be looking at, while the compiler can do it.

I saw that C++11 replaced exception specification with noexcept, so now I can turn off exceptions in just part of the code. Nice, I don't have to disable them globally :)

As for cleanup by destructors, that works for stack allocated stuff, but as soon as you use new you have to call delete somewhere, right ? And a catch clause sounds like the right place. (I saw C coders use goto to emulate it...)
bottom line: disregard c++ exceptions.
added on the 2012-05-11 23:20:24 by superplek superplek
Quote:
As for cleanup by destructors, that works for stack allocated stuff, but as soon as you use new you have to call delete somewhere, right ? And a catch clause sounds like the right place. (I saw C coders use goto to emulate it...)


First of all avoid heap objects when you don't need them. If you do need a heap object, consider using an auto_ptr or wrapping the allocation+cleanup in your own class that you then allocate on the stack. It sounds a little tedious, but it makes a lot of sense when you get used to it. RAII

For more complex lifetime management you could use reference-counted smart pointers. But be careful because people tend to get overexcited when they discover how cool smart pointers are. And that leads to the dark side.

Whatever you do, though, a catch block is a bad place for cleanup. For one thing it forces you to do the cleanup in two places (once for regular program flow, once for exception cases), and also of course it catches the exception, so you need to rethrow it.. it gets messy. You could say that if you need to do cleanup at all outside of destructors, then you're on the wrong track. But then again, only a Sith deals in absolutes.
added on the 2012-05-11 23:55:35 by doomdoom doomdoom
less talking about general coding style BS, more writing good code.
added on the 2012-05-12 07:14:22 by ryg ryg
I actually find this thread quite interesting. Besides Revision 2013 is still a long way away.
added on the 2012-05-12 11:02:22 by trc_wm trc_wm
doomdoom: thx for the talk on object oriented design and factory method. i absolutely agree with many issues you pointed out (usage of factory methods and initialization methods). at work i code Java (and other JVM languages), and i barely use constructors. instead i use factories or dependency injection, which ends up in NO OVERHEAD at all, just writing something like this:
Code:@Inject FancyComponent foo

this makes full use of polymorphism as you specify the implementation to be injected somewhere else. this is a pretty high level feature (and i'm sure, as always, it can be emulated in c++) and one of many reasons why i actually prefer languages like Java or Xtend if I want to write code efficiently (and readable ;). now, when it comes to democoding (which i'm not a master in), i still prefer C due to the imperative nature of demos and because, being used to pretty high level OO features, coding C++ just feels awful to me.
added on the 2012-05-12 11:09:30 by skomp skomp

login

Go to top