Hacker Newsnew | comments | ask | jobs | submitlogin
Why should I have written ZeroMQ in C, not C++ (250bpm.com)
373 points by rumcajz 710 days ago | comments


haberman 709 days ago | link

I struggled with a similar question for my low-level protobuf library UPB (https://github.com/haberman/upb). Here are some blog posts describing my dilemma about this: http://blog.reverberate.org/2009/11/porting-upb-to-c.html http://blog.reverberate.org/2009/12/torn-over-c-question.htm... I ultimately chose to stick with C, and I'm happy with my decision.

When people ask me why I stuck with C, it's difficult to give a single overwhelming reason why C is better. The most clear-cut one is that C++ is not supported in the Linux kernel and I want my library to be usable there. But the other reasons why I prefer C are not as easy to quantify.

One reason is that code written in C is more transparent. You can look at some C and know what it is going to do (add some numbers, call a function, etc). Macros are the one exception to this, but good C programs use these sparingly and clearly mark the macro with an UPPER_CASE_IDENTIFIER. C++ is much harder to "see through." Any function call could end up throwing an exception which does a non-local exit from the function. Any addition, assignment, pointer dereference, etc could actually be an overloaded operator. Any function call could be performing a user-defined implicit conversion on any of its parameters. For overloaded functions, the compiler decides what function is actually called by applying non-trivial pattern matching rules. The runtime will automatically invoke any static initialization code (like constructors of global statics) pre-main in an undefined order. etc. etc.

I intend to get a thorough security review of my library once its design stabilizes. I asked Tavis Ormandy (a fellow Googler and security badass) which language is easier to audit for security and he said C for the reasons of transparency I listed above.

People often say that you can just avoid using the parts of C++ that you don't like. That is true, but hard to enforce, especially on a multi-person project. If you're asking others to follow your set of rules, you have to write those rules down in meticulous detail, leading to something like the Google C++ style guide (http://google-styleguide.googlecode.com/svn/trunk/cppguide.x...), and at that point you're asking your contributors to study and internalize a large and complicated list of what is and isn't allowed just for the privilege of contributing to your project. I don't think this approach scales to lots of projects that are all choosing a different subset of C++.

There are definitely things I miss by not having C++'s facilities. Constructors and an implicit "this" parameter are both really nice. Constructors are remarkable in that you can write a single function that gives you three different construction patterns for free (direct construction, heap construction with "new", construction in an arbitrary location with placement new). Automatic upcasts are nice and totally safe. Inheritance and public/private specifiers let you express directly lots of things you can only express with comments in C.

But overall I think that using straight C is the right choice for a project like my library UPB.

-----

cperciva 709 days ago | link

good C programs use [macros] sparingly and clearly mark the macro with an UPPER_CASE_IDENTIFIER

This is only necessary if (a) the macro has side effects, or (b) the macro interprets its arguments multiple times. In cases where the macro is functionally equivalent to an inlined function call, there's no need to capitalize it.

asked Tavis Ormandy (a fellow Googler and security badass) which language is easier to audit for security and he said C for the reasons of transparency I listed above.

I agree on both counts: C is much easier to audit, and Tavis is indeed a security badass.

-----

aerique 709 days ago | link

C is superior for libraries since a lib can then be used from so many other languages. A C++ library that doesn't provide a C API is an absolute nightmare to write bindings for.

-----

cbsmith 708 days ago | link

So true. Binding to Python is such a pain: http://www.boost.org/doc/libs/1_49_0/libs/python/doc/. Lua is even worse: http://www.rasterbar.com/products/luabind.html....

Seriously, once you know how to use it, C++ actually makes binding to a lot of higher level languages a lot nicer than doing so with C.

-----

p_l 707 days ago | link

... I am sorry, but that's very untrue.

As it happens, not every language is implemented using runtime written in C/C++, nor does every language modules written in C/C++ to extend it instead of FFI.

Dealing with libraries written in C++ without C API is one of the worst PITAs in my experience, leading to crazy things like KDE's SMOKE (which, to make it funnier, has bad documentation, so I'm still unsure if I can make it work outside Qt's object model)

-----

cbsmith 706 days ago | link

> ... I am sorry, but that's very untrue. > > As it happens, not every language is implemented using runtime written in C/C++, nor does every language modules written in C/C++ to extend it instead of FFI.

You did an awesome job tearing apart that strawman, but you are misrepresenting what I said. That second sentence is in no way in conflict with my "very untrue" statement.

To refresh, with emphasis added for the reading impaired:

    C++ actually makes binding to *a lot* of higher level languages a lot nicer than doing so with C.
That you can find a distinct subclass of languages where you believe that it is hard to do bindings to C++, doesn't mean the above statement is even mildly untrue, let alone very untrue. Please review and reconsider:

* There are certainly a lot of higher level languages which don't qualify for your definition. * SMOKE is actually a binding through Qt's MOC, which is generally considered by the C++ community to be something which makes Qt pretty un-C++-ish. You are right to phrase it as "Qt's object model", because it sure as heck isn't C++'s.

Try looking at the LuaBind and Boost.Python libraries. They represent a very different approach to language binding, and it isn't dependent on there being a wrapper C API. While writing binding framework for a given language is a bit of a PITA, once that has been built, it really does make binding to a high level language a lot easier than trying to do it through a wrapper C API.

-----

malkia 709 days ago | link

Throwing C++ exceptions across DLL boundaries could also be problematic. Note I say DLL, and accent on Windows, because on other systems more likely one C++ runtime to be used, always dynamically linked (well, there are exceptions like busybox - but they are not even written in C++).

Anyway, in the Windows world - a mix of compilers is the norm, and even the "standard" MSVC compiler differs in how it handles exceptions one version to another.

For example all the team is on VS2010, while I keep VS2005 and VS2008 because I have to compile plugins for Autodesk products released with these compilers (not only for exceptions, but also for RTTI, virtual functions handling, and Runtime overall - for this even "C" would get the blame).

-----

el_presidente 709 days ago | link

On Linux you can mix compilers as long as they implement the Itanium ABI. At least I haven't had to recompile anything after upgrading GCC.

-----

gaius 709 days ago | link

Not C++ compilers tho' - the issue is not the ABI, it's the name mangling. Tho' I believe Clang and GCC have decided to use the same convention, ICC might not have (and that by all accounts is a great compiler).

-----

el_presidente 709 days ago | link

http://sourcery.mentor.com/public/cxx-abi/abi.html#mangling

I'm 99% sure that ICC is compatible with GCC.

-----

vetinari 708 days ago | link

When the switch between GCC 2.x and 3.0 happened, the big new thing was 'industry standard' C++ ABI (called Itanium ABI, as el_presidente mentioned).

-----

stcredzero 709 days ago | link

you have to write those rules down in meticulous detail,... and at that point you're asking your contributors to study and internalize a large and complicated list of what is and isn't allowed

What about a short list of what is allowed, with a strict whitelisting policy? I recall from years ago a version of the Taligent C++ conventions that was fairly compact. (Which was also the subject of Bjarne Stroustrup's quip about trying to use C++ as "a poor man's Smalltalk.")

Automated tools that embody the blacklists might be useful here as well, provided that they can be scripted quickly. I'm not sure if there are parser-based tools for C++ that would fit the bill. The article's problems with non-empty constructors seem amenable to automated blacklisting. Unfortunately, even with such tools, this strategy entails lots of work up-front by project organizers.

-----

BMarkmann 709 days ago | link

To save someone else the trouble of googling:

http://pcroot.cern.ch/TaligentDocs/TaligentOnline/DocumentRo...

-----

stcredzero 709 days ago | link

These aren't the conventions you are looking for. (The ones I remember call for all member functions to be virtual.)

-----

evincarofautumn 709 days ago | link

This is absurd. Exceptions are just generalised returns. You can use them well and benefit from them, or use them poorly and make the stuff of nightmares.

It’s the same with any feature of any language. You can choose not to use exceptions in your code, if that makes it easier for you to understand and manage, but it’s certainly not the fault of the feature in general.

Basically, it’s up to you to structure your code well. Yes, it gets bad if a potentially throwing function can be called from multiple points. The error needs to be handled at every one of those points, and probably in much the same way.

But if you see repetition, why aren’t you factoring it? And why are you calling that function from multiple unrelated locations in the first place?

I get it. I’ve used C++ for half a lifetime—it’s a hopelessly broken language. There are tons of things about it that just make me furious. But I can get stuff done in it because I’ve taken the time to understand it in depth. Don’t complain about the language or a particular feature thereof just because it’s not suited to your problem or your way of thinking.

There are other languages out there (Erlang) that are far better suited to stable zero-downtime systems than C++. There are languages (Haskell, Lisp) that help you better manage complexity than C++. Every language implementation worth its salt has C bindings, so you can write performance-critical code at a low level, while still managing the whole system in a high-level language. There are options. Explore them!

-----

dkarl 709 days ago | link

Agreed. His examples are nonsensical, in almost every case comparing handling an error in C with signalling an error in C++. To see what I'm talking about, here's his first comparison:

    // C
    int rc = fx ();
    if (rc != 0)
        handle_error ();

    // C++
    int rc = fx ();
    if (rc != 0)
        throw std::exception ();
This is absurd. This is not the same thing in two different languages. These two pieces of code do two different things. One handles the error, and the other signals that there's an error that should be handled elsewhere. For an apples-to-apples comparison, let's consider both cases in each language. If this is the appropriate place to handle the error, then you handle it. It looks like this:

    // C
    int rc = fx ();
    if (rc != 0)
        handle_error ();

    // C++
    int rc = fx ();
    if (rc != 0)
        handle_error ();
I.e., exactly the same. If you DON'T want to handle the error here, then you return an error code or throw an exception:

    // C
    int rc = fx ();
    if (rc != 0)
        // There's no way to know where or whether this will be handled
        return some_error_code;


    // C++
    int rc = fx ();
    if (rc != 0)
        // There's no way to know where or whether this will be handled
        throw some_exception();
You're in the same boat in C and in C++. The error must be handled elsewhere, and nothing you do here can ensure that it will be. Exceptions have some advantages, though:

1. You do not have to anticipate inconvenient collisions between error return values and valid return values.

2. If the calling function is not the appropriate place to handle the error, then no boilerplate is required in the calling function to pass the error up the stack.

3. Exceptions let you use destructors instead of explicit cleanup blocks.

4. Failure to write error-handling code will not result in an operation proceeding in an invalid state, deceptively reporting success, or silently corrupting state. An error return value can simply be ignored. An unhandled exception will cause a cascading failure that cannot be mistaken for successful completion of an operation.

5. You can always use return values to report errors if they happen to be preferable in some cases.

P.S. The more I understand his complaints, the more I think he is just trying to get too fancy. If construction and destruction are problematic for some objects, then classes with trivial constructors and destructors give him exactly the struct-like behavior he wants, so what is wrong with that? How in the world do you get into worries about exceptions thrown from constructors and half-initialized objects (exception in the sense that any allocated-but-not-initialized struct is in a "half-initialized" state)? This is like saying knives are too dangerous to have the in the kitchen because you are in the habit of thrusting your hand blindly into the knife drawer.

-----

evincarofautumn 709 days ago | link

Superbly put. I cannot upvote this enough.

-----

Blunt 709 days ago | link

I second your Superbly put. I cannot upvote this enough too. I've been doing C and C++ for over 17 years from embedded up to CRUD accounting type apps.... Spot on my friend, spot on!

-----

pjmlp 708 days ago | link

Well put.

-----

wvenable 709 days ago | link

I agree the whole rant about exceptions just seems wrong -- if you've got a state that you can handle right there in the function itself then it's not an exception. You just deal with it. The C code and the C++ code would be equivalent.

This feels more like a "Oh I have exceptions, I better use them" situation. And his solution is to pick a language without exceptions so he doesn't have to use them. It's all very odd.

-----

danssig 709 days ago | link

Or in Common Lisp you would be able to "signal" a condition and then handle the situation. Signal will just do nothing unless some higher level code has an explicit handler for it [1].

[1] I remember reading a MIME encoding library years ago. There was a point where the code would detect an error but if the error was at the end it would just drop that part and write in the last couple of bytes. In this situation, most clients won't care that this is happening but what about those that do? Since this code signalled error by returning NULL there was no way to signal "saw an error, but fixed it". In Common Lisp, signal could have been used for clients that would want to report on this.

-----

calinet6 709 days ago | link

Bingo. When I read this:

"C++ exceptions just didn't fill the bill. They are great for guaranteeing that program doesn't fail — just wrap the main function in try/catch block and you can handle all the errors in a single place."

That's not C++'s fault, or Exceptions' fault in general: that's your fault for using an extremely useful and versatile language construct completely incorrectly.

If a high-level language is bad because it offers you more functionality and features than you know how to use, then sure, maybe you shouldn't be using a high-level language. But this argument is almost like decrying being rich because you don't know how to use your money wisely; trust me, it's still better to have money, and you shouldn't throw away your high-level life just because it's "simpler to be poor."

If your goal is to do high-level complex things, it's better to use a high-level language. If your goal is to do low-level pedal-to-the-metal things, then it's better to use a low-level language. But the simple fact that a high-level language is high-level is not a valid criticism. Not of the language, anyway.

-----

ryanmolden 709 days ago | link

>just wrap the main function in try/catch block and you can handle all the errors in a single place.

Yeah, this kind of statement always scares me. In my experience people proposing it tend to 'handle it' by simply ignoring it happened. As if by simply catching and not crashing you have averted disaster. Nevermind that very little, if any, code is truly exception safe. That exception just tore through N frames of code interrupting each one at who knows what step and who knows what kind of state the object is now in. If you were lucky they were at least using RAII objects to ensure they haven't leaked things, but that is only half the battle. Unless they are using some kind of transactional approach to mutating their own instance state you likely have an object in a 'half-transitioned' state, which will likley violate various invariants the original author assumed (incorrectly) would always hold. Continuing in C++ (or really any language) after swallowing an exception 'around main' is a supremely bad idea, unless you like heisenbugs or getting pwned.

-----

felipemnoa 709 days ago | link

I agree with your entire post. This part made me laugh though:

>you shouldn't throw away your high-level life just because it's "simpler to be poor."

It made me think of people that have won the lottery and after they've finally blown through all their winnings many concur that: it's "simpler to be poor."

-----

cageface 709 days ago | link

The combination of manual memory management and exceptions is particularly nasty.

When you can't count on a GC to clean up for you every function has to be carefully coded to prevent potential leaks. Browse through some of Herb Sutter's GOTW questions to get an idea of exactly how tricky this can be.

-----

evincarofautumn 709 days ago | link

Idiomatic C++ does not rely on manual memory management—it relies on deterministic memory management. Use unique_ptr, or shared_ptr when it makes sense (hint: it rarely does).

-----

cageface 709 days ago | link

In theory you have every single leakable resource wrapped up in a RAII container or a smart pointer. In reality you're almost always dealing with raw pointers coming from a lower level C API or old C++ code or some kind of object handle you haven't wrapped yet.

Of course the recommendation from Stroustrup & Sutter is to use the new smart pointers for everything but I think it will be a few years at least before most people can follow that advice.

-----

evincarofautumn 709 days ago | link

Typically what I do in that situation is lightly wrap the API. For instance:

    Image* image = allocate_image(…);
    …
    deallocate_image(image);

    ↓

    struct ImageDeleter {
        void operator()(void* image) const {
            deallocate_image(image);
        }
    };

    unique_ptr<Image, ImageDeleter> image(allocate_image(…));
That certainly can’t work everywhere, but I have yet to be failed by it.

-----

jmq 709 days ago | link

You can reduce verbosity of the code by passing destructor function directly to unique_ptr constructor:

    std::unique_ptr<Image, void (*)(Image *)>(image, deallocate_image);

-----

evincarofautumn 708 days ago | link

Cool, thanks for the tip. I meant to offer an example for the general case.

-----

basman 709 days ago | link

Or, perhaps easier:

scoped_ptr<Image> image(allocate_image(...));

-----

jongraehl 709 days ago | link

People who don't do this are just asking for it.

-----

malkia 709 days ago | link

That's a lot of boilerplate code. Okay there are macros to wrap such things, but I guess it's things like this that would make the future reader of the code puzzled.

-----

cbsmith 709 days ago | link

jmq & basman provide examples of how to do it succinctly.

-----

ajross 709 days ago | link

Sure. And using deep-thrown (i.e. thrown across module/library boundaries -- often exceptions are local tricks used within tightly coupled code) exceptions in an application where you're dealing with such pointers would be bad design.

-----

mccoyst 709 days ago | link

One of his first examples is additionally absurd and is either the flimsiest of strawmen or he simply doesn't understand when exceptions should be used:

    try {
        ...
        int rc = fx ();
        if (rc != 0)
            throw std::exception ("Error!");
        ...
    catch (std::exception &e) {
        handle_exception ();
    }
Why is he throwing exceptions and catching them within the same function? His "C equivalent" is what he should've been doing in C++.

-----

onli 709 days ago | link

He states how he understood exceptions:

"C++ exceptions just didn't fill the bill. They are great for guaranteeing that program doesn't fail — just wrap the main function in try/catch block and you can handle all the errors in a single place."

This is something i learned in my very first computer science lecture not to do.

To be fair: It's what exceptions can be good for - a last barrier before a crash and a way to handle errors later. But you're absolutely right that he simply could react directly to the error. Or throw specific exceptions and react to them. His described issue has nothing to do with exceptions themselves, just with the way he thinks he has to use them.

-----

cobrausn 709 days ago | link

Though we probably learned this is not the way to use them, I have seen numerous real-world examples where this was the case.

Not only that, I don't think C++ exceptions are necessarily very good at being a 'last barrier' before a crash - they simply can't catch all the errors, because they are good at indicating conditions that might be recoverable, not catching and recovering from logic errors that would cause a crash. There are plenty of ways to hang yourself in C++ without ever throwing an exception.

-----

figglesonrails 709 days ago | link

"Though we probably learned this is not the way to use them, I have seen numerous real-world examples where this was the case."

Ah, Java code at my workplace.

   catch(Exception e) {}

-----

LoneWolf 709 days ago | link

I believe you mean catch(Throwable t) {}

-----

figglesonrails 709 days ago | link

Exceptions indicate //exceptional conditions//, i.e. conditions other than the intended state transitions of the program. They aren't a guard against crashing, they are a mechanism for formalizing error states in the program.

Memory corruption? Appropriate response: fail. Logic error? Appropriate response: fail. Exception? Appropriate response? Decide if program can continue, respond.

I'd say the bigger issue is less of "exceptions" so much as "states". Adding any kind of error handling increases the number of state conditions that your program may be in, and that complexity is obscenely hard to manage. I don't think C or C++ or Java or Erlang or any other language will free you from the burden of proper software engineering.

-----

batista 709 days ago | link

>He states how he understood exceptions: "C++ exceptions just didn't fill the bill. They are great for guaranteeing that program doesn't fail — just wrap the main function in try/catch block and you can handle all the errors in a single place." This is something i learned in my very first computer science lecture not to do.

Yes, and he learned not to do it also, thank you.

He makes a tongue in cheek comment about a common Exceptions abuse. Actually, in the very next sentence after than one, he explains why that is bad.

Do people on Hacker News believe that the guy doesn't know C++? He is the frigging author of one of the most useful, used, high quality C++ libs.

-----

onli 709 days ago | link

>Actually, in the very next sentence after than one, he explains why that is bad.

No, he doesn't. The very next sentence is:

"However, what's great for avoiding straightforward failures becomes a nightmare when your goal is to guarantee that no undefined behaviour happens."

-----

batista 708 days ago | link

And that's it, there's nothing more to it.

It's great if your goal is to avoid straightforward failure (= crash), it's bad if your goal is to also avoid undefined behavior (= invalid program state).

Have you discovered something else on the matter?

-----

onli 708 days ago | link

Of course there is. Have you actually read that discussion here? If you don't use exceptions in the way he described them, you don't have undefined behaviour (general big exception handling at the end vs granular error-checking, which is of course also possible with exceptions). Everything he described has nothing to do with exceptions, but with the way he uses them in the examples he showed.

-----

batista 708 days ago | link

>Of course there is. Have you actually read that discussion here?

Yes, and as a Comp Sci, and developer of 15 years, I knew it all before. It's not like the discussion got into any advanced territory.

>If you don't use exceptions in the way he described them, you don't have undefined behaviour (general big exception handling at the end vs granular error-checking, which is of course also possible with exceptions). Everything he described has nothing to do with exceptions, but with the way he uses them in the examples he showed.

Which is the ways that are relevant to his project and coding style. People on the HN thread were just quick to second-guess him without understanding the problem domain and his constraints (which were: "I want to use exceptions in X way for them to be worth to me. I won't even consider the Y way --what some in the HN discussion proposed-- because it's convoluted, requires far more code and maintenance and if I have to do that, I might as well do it in C".

For example, you cannot wrap each and every spot with a try/catch because this doesn't buy you anything over the C way of error checking.

Also, you cannot know what exactly occurred (at coding time, not runtime by checking the stack), unless you granulize (granuralize?) exceptions to death. But then, why not just check the error conditions/codes? And if you do so, what you get over C?

etc...

-----

politician 709 days ago | link

An ellipsis means something unimportant has been elided. For example,

  try {
      foo();
  catch (std::exception &e) {
      handle_exception ();
  }

  int foo () {
      int rc = fx ();
      if (rc != 0)
      throw std::exception ("Error!");
  }

-----

rocha 709 days ago | link

I agree, it was disconcerting to me as well. I read again and now I think that he is putting the throw there to exemplify that fx() can throw an exception.

-----

Peaker 709 days ago | link

Returns are statically typed. Exceptions are not.

In a static language like C++, this is a big disadvantage.

-----

malkia 709 days ago | link

It's not absurd. Just the fact that C++ exceptions were used, would make this code not portable - not being able to run on some very popular devices.

-----

0xABADC0DA 709 days ago | link

Checked exceptions are great, but unchecked exceptions are a huge disaster only mitigated by failsafe features such as RAII or garbage collection with finalizers. Sometimes like in highly dynamic languages the failsafes are enough that it's ok to just use unchecked exceptions, but in a language like C++ you really can't afford to make mistakes in the first place.

The process of adding an exception in Java:

1. throw TheException

2. compile

3. add "catch(TheException e)" to fix errors or "throws TheException" to signature

4. repeat 2 until no more errors

Compare to C++:

1. throw TheException

2. examine all functions to determine if they might need to handle TheException or consult intuition about program structure

3. repeat 2 until 'done'

So the real problem with exceptions in C++ is the compiler can't tell you where to look next and what you forgot about. Step 2, magically find all the places that should catch the exception, is both time consuming and error prone.

I find it strange that C++ has all kinds of strictness with types (have to cast void*, const virus, etc) but with exceptions they just throw caution to the wind.

-----

MatthewPhillips 709 days ago | link

Java is the only language that I can think of that gets exceptions right, in that it forces you to acknowledge them every step in the chain and choose whether you're going to catch them or throw them upwards.

-----

SeanLuke 709 days ago | link

Strange. It seems to me that the consensus in the Java community is that checked examples were among the worst misfeatures of the language.

Googling for ---java checked exceptions--- is illuminating.

-----

JamisonM 709 days ago | link

It is strange that the consensus in the Java community sort of ended up being against checked exceptions. I for one think that this consensus is completely wrong and I suspect that what looks like a consensus is just a bunch of very loud people shouting down everyone else (and to me not making very compelling arguments). Elliotte Rusty Harold's brief post on this pretty much nails it for me: http://cafe.elharo.com/blogroll/voting-for-checked-exception...

-----

nostrademons 709 days ago | link

The problem is that it's a trade-off - it's not a case of one side being right or wrong. Checked exceptions introduce API fragility: when the implementation of a method changes in a seemingly inconsequential way (it may now throw an error), then all it's callers and all their callers, transitively need to have their signatures edited. What should be a simple matter between just the leaf method and the top-level now has to involve all the code between them, which may not even be in code you authored or controlled.

Unchecked exceptions, however, mean that code that would previously always succeed may now never even be executed. Which makes it difficult to reason about the behavior of your function.

It's the same trade-off that appears in several other areas of language design. Do you force developers to write down their assumptions in the source code, so that everyone reading it knows exactly what's going on? Or do you let them keep it in their heads, so that it can change easily when the system requirements change? Dynamic vs. static typing, global variables vs. parameters, polymorphism vs. conditionals, default arguments vs. explicitly specifying parameters - it's all the same fundamental argument applied to different language constructs.

Which side your on usually ends up depending on whether you read more code or write more code. Maintenance devs always want things to be explicit, because it makes it easier for them to grok the whole codebase and make the change they need to do. Green-field innovators always want things to be implicit, because it means they have to specify less, and everything they specify tends to end up changing anyways. Sometimes the same person ends up cursing both ends of the divide depending on what they happen to be doing at the time. I do a bunch of speculative prototyping for Search Features at Google; in this role, 90% of my code never sees the light of day, and so I write it with tools like Python or JQuery that let me easily change things around and not specify too many of the details. I also do a fair bit of infrastructure work on the search stack; in this role, I write barely any code and spend the bulk of my time tracking down where an obscure piece of data is coming from and how it needs to be modified to add a new feature.

-----

JamisonM 709 days ago | link

It is a trade-off, on that I agree with you and I do understand that checked exceptions do tend to propagate implementation-level details up where that might be undesirable. I also agree that in green-field situations it can be awkward figuring out what to do with an IOException that you suspect will not exist in a few hours.

The problem as I see it is that if you advocate the alleged consensus that checked exceptions are evil then you are taking the position that there is no trade-off. Checked-exception lovers like myself acknowledge this grey area and the design challenges it presents. That is a big part of why I believe that a Java with checked exceptions is more "reality-based" than one without them, it recognizes the challenge and gives an API designer the tools to do the right thing. I want to have that hammer in my tool chest instead of banging on things with the back of my hatchet.

-----

MatthewPhillips 709 days ago | link

That's interesting. I'm not a day to day Java programmer so my perspective is different from the Java community's. I have ran into problems numerous times in other languages where an exception isn't discovered until runtime in production because there's simply no way of knowing whether a library's function might throw an exception or not. The workaround is to be overprotective. I've always envied that Java devs know what to expect from every function they call

-----

Anderkent 709 days ago | link

Your java library might still throw unchecked exceptions, so you have to be as careful as if all exceptions were unchecked.

-----

evincarofautumn 709 days ago | link

Haskell does this also, and its type system is much more expressive than Java’s, enough so that exceptions are not (exclusively) a built-in language feature.

-----

aidenn0 709 days ago | link

Read up on exception specifiers in C++ and reconsider this comment.

-----

davedx 709 days ago | link

I just did; what I couldn't figure out, was is there a way to specify multiple different exception types in your C++ throw()?

In Java, you can do:

public int myfunc(int a, int b) throws IOException, InvalidGoatException {}

How would you do this in C++?

-----

cbsmith 708 days ago | link

You can do much the same thing, only the meaning is the reverse.

-----

signa11 709 days ago | link

> @davedx asks : is there a way to specify multiple different exception types in your C++ throw()?

yeah throw() can contain multiple types that can possibly be emitted e.g. int foo() throw(a,b,c,d) implies that foo can only throw either a/b/c/d types.

-----

Peaker 709 days ago | link

Except that is only enforced at runtime, and converts the exception type to unexpected_exception, which is even worse!

-----

pubby 709 days ago | link

Exception specifiers are deprecated and for good reason.

-----

shin_lao 710 days ago | link

It looks mainly looks like a rant on exception, but nothing forces you to use exceptions with C++. You can even allocate memory in a way that doesn't throw.

We don't use exceptions for the reasons described in the article (and many others), we use boost::system::error_code instead.

But that's for the first half of the article, and the OP admits that he simply gave up on exceptions.

As for the part about half-initialized state, I think the problem is that the author is trying to do OOP in C++ without exceptions. Perhaps a more generic programming approach would solve his "half initialized problem".

Don't want to sound like a snob, but it really looks more like a design problem than a language problem.

-----

zmmmmm 709 days ago | link

> nothing forces you to use exceptions with C++

But unless you eschew every other C++ library including the STL, etc. other code is going to (potentially) throw exceptions whether you like it or not. C++ has exceptions built in at its most basic level - operator new() throws bad_alloc. Are you going write C++ with using 'new'? Are you going to ensure that no library you use uses 'new'?

I used to follow Herb Sutter's "guru of the week" problems and a large percentage of them were "how many exit paths are there from this block of code?" which would usually be a function about 4 lines long. Typically there were > 20 ways the function could exit, taking into account all the various operator overloadings, implicit constructors / type conversions, etc. And that was using code that itself usually had no use of exceptions - their mere presence in the language caused the problem.

-----

Aloisius 708 days ago | link

I have seen exactly 0 programs that ever wrapped new calls in try/catch blocks to catch bad_alloc. If you can't allocate memory, you are likely going to need to exit anyway.

You can write quite a lot of C++ without ever using exceptions since exceptions. There are people who write exception-happy code. I tend to prefer only using exceptions for exceptional events like where the the only other option would be for the program to exit. I throw an exception in the hope that someone upstream will catch it and be able to handle it.

Honestly, if you throwing exceptions constantly as part of your normal executing code path, you're going to drive yourself crazy.

-----

zenogais 709 days ago | link

Agree. I've often opted out of using exceptions in C++ for high performance or low level systems. This usually meant coding C in C++, but it was still great to have namespaces, the STL, Boost libraries, and stricter static typing. Too often it seems that people think they have to be at one extreme or the other of the C - C++ spectrum - why not something in between?

-----

dbcfd 709 days ago | link

> Don't want to sound like a snob, but it really looks more like a design problem than a language problem.

I stopped reading about halfway through as most of the code samples reinforced this.

-----

pjmlp 709 days ago | link

I agree.

For me it looks more a blame the tools rant, instead of looking at improving the design.

All the alternatives proposed would work as well in C++, with the added benefit that C++ has a better type checking than C.

-----

vz0 710 days ago | link

I agree, this is just a rant on C++.

Coming from Java world, I can tell that it is nice being able to raise exceptions from the constructor.

However, switching from C++ to C is absurd. C++ is (mostly) a super set of C. Whatever you want to do in C, in C++ is also possible.

If there are some C++ features (exceptions, classes, contructors, etc.) that are making his coding task more complicated, he should simply stop using those features and refactor his code.

-----

ufo 709 days ago | link

There is one advantage to writing in plain ANSI C and it is that you get it to compile and run on anything.

-----

antidoh 709 days ago | link

I wouldn't say absurd. Certainly you can use C functions in C++. But if you want to eliminate the use of most or all C++-only features, the only way to guarantee that is to not use C++. White or blacklists can be circumvented, intentionally or inadvertently.

-----

Locke1689 709 days ago | link

Going forward, I think C++ is not a bad choice, but I can definitely see the distaste for exceptions. Unfortunately, as the author notes, simply not using exceptions doesn't quite do it. Fortunately, the hard work has been done for you. Much of Google's code was written in an exception-free manner before newer evolutions of C++ came out. This means that they have banned use of exceptions in their style guide. Luckily, it also means they've developed workarounds for most of the exception pain points.

I would encourage everyone to take a look at the Google C++ style Guide[1]. Most of the complaints are addressed in that article. Constructors can only signal errors through exceptions, so Google C++ allows only basic initialization in the constructor -- all real work is done in an explicit Init() method which can signal an error. I believe the guide addresses the posters other concerns too.

In general, the Google C++ style guideline is a very good resource for writing consistently good C++.

[1] https://google-styleguide.googlecode.com/svn/trunk/cppguide....

-----

drucken 709 days ago | link

Agreed, it is a very good guide.

However, the ZeroMQ author goes a lot further than that for his specific application.

He made some reasonable arguments that even the use of OOP/classes in C++ makes catching undefined behaviour unnecessarily difficult for systems programming.

-----

Locke1689 709 days ago | link

His other objections didn't make a lot of sense to me. Destruction doesn't seem like something that can fail. Your object has been deleted or it is going out of scope. You don't even manually call the destructor. It should be about freeing resources. what work is he doing in the destructor that can fail?

-----

gchpaco 709 days ago | link

Well, fclose can fail, for example; it can as part of the closing process flush its buffer and that can fail like any other I/O process. Further, sometimes the error than happened during a previous write(2) call is only detected in the close(2); think NFS and quotas, or check the man page. So already we have errors that occur during teardown that need to be handled somehow.

Similarly one can imagine using RIAA to hold a file lock, or a temporary directory; this is a perfectly reasonable thing to do but there are dozens of reasons why the final rm -rf of the tempdir could fail.

The idea that somehow object teardown is immune to failure is overly naive, and the fact that you can't accommodate it easily in RIAA is quite disappointing and makes the whole paradigm much less useful for carefully coded software.

-----

Locke1689 709 days ago | link

My claim is that object teardown is not what the destructor is for. Teardown encompasses many things that are not resource releasing. Destructors are for releasing resources, not necessarily guaranteeing their destruction.

For example, fclose can fail, but even if it does, the stream given is no longer held. In fact, any further use of the stream is undefined. There is certainly a place for logging in this situation, but if close was a behavior of the class, it should have a separate close function.

As far as locking goes, it is the responsibility of the locking routine (probably a semaphore) to release the callers hold on the lock, but it is not the callers responsibility to ensure that whatever resources are associated with the lock (e.g., a file) actually get deleted. That is the locking object's responsibility.

I agree that some aspects of RAII could be better, but C is not the answer to that. C doesn't even have scope-based RAII.

-----

pmjordan 710 days ago | link

This strikes me as throwing the baby out with the bathwater. While the criticisms are valid, and people love to hate C++ for all sorts of reasons (I avoid C++ exceptions altogether), C++ is for practical purposes a superset of C, so if a C idiom works better than its C++ equivalent, you can just use it instead. I've been working on a project which has necessarily been written mostly in pure C (kernel code), and I do really miss C++'s extra type safety. (GCC has some type warnings that help, but it's still not as safe)

Then again, maybe an entirely different language, geared towards high availability, such as Erlang or Ada, might have been a better choice in the author's case.

-----

dap 710 days ago | link

Just because C++ is a superset of C doesn't make it strictly better. The presence of additional features can be a liability. The obvious problem is that if you want to use the C subset in a multi-person project (whose team evolves over time), you have to create a way to enforce that. Another example is that it's more difficult to produce static analysis tools like lint. (As an example, lint in C detects unused stack variables. This is trivial for C. This is extremely difficult for C++ because the mere construction of the object on the stack often has important side effects.)

-----

Jach 709 days ago | link

It's not even a superset. While not as bad as Netscape changing LiveScript to JavaScript to market off of Java, "C++ is a superset of C!" is still a marketing gimmick. At least with C++11 everyone I've heard talk of it agrees it's a different language now, maybe people will stop bundling "C/C++" on the resume together. For anyone curious about a few other trivial problems of C and C++ incompatibility, check out http://yosefk.com/c++fqa/picture.html#fqa-6.11 (The full FQA there is quite good, being based off correcting/questioning the frequently cited C++ FAQ, so if you want to actually learn C++ more than it probably deserves the FQA will help you along the way. The full section on mixing C and C++ is here: http://yosefk.com/c++fqa/mixing.html )

-----

megrimlock 709 days ago | link

What you describe as a marketing gimmick has let us gradually migrate a 10M line cross-platform C codebase to C++. There are plenty of reasons to dislike C++ but casually disparaging them often means you miss reasons for its continued real-world success.

-----

pmjordan 709 days ago | link

While it isn't a strict superset, there aren't any C features you can't use in C++, unless you count lack of pointer type safety as a feature. This was the original point I was making (and note how I didn't say it was an actual superset).

-----

dlsspy 709 days ago | link

The two ways (C90 and C99) you can create a variable length item at the end of a structure in C are invalid C++. You can't express them at all.

I work around it by making a one element array and using "sizeof(MyThing) - 1" everywhere when I want to reference the size then use placement new and the ::new operator to do the allocation and initialization of my object.

It works, but it's not very straightforward.

In general, I rather like C++, but it does make a few things harder than C.

-----

ajross 709 days ago | link

You can't do it for structure fields in C++. The C++ idiom would probably be something like a member function that returned (void*)&this[1].

-----

__david__ 709 days ago | link

Of course there are: C99 initializers are heavenly and they don't exist in C++.

-----

dap 709 days ago | link

Yes -- see my post above. Syntax and semantics simple enough to be easily statically analyzed (both by humans and tools like lint) are a language feature that C has over C++.

More generally, many people see significant value in the very property of C that it doesn't let you do things that C++ lets you do. That's why the argument that "C++ is a superset" and "there's no good reason not to simply use a C++ compiler on your C code" is bogus.

-----

pmjordan 709 days ago | link

If you think your biggest problem is forcing your team to use the prescribed language or language subset, it probably isn't.

-----

cageface 710 days ago | link

The thing is, if you use the subset of C++ people usually recommend, you're hardly better off than with C. Turning off exceptions means you can't use the STL (alloc errors throw) and you can't do any non-trivial work in your constructor so you have to manually initialize everything anyway.

-----

ComputerGuru 709 days ago | link

Not true. Many STL distributions have a compile-time flag you can set to disable all exceptions. Otherwise, there are entire STL distributions you can compile against that are exception free.

See here for many solutions: http://stackoverflow.com/questions/553103/can-i-disable-exce...

-----

pmjordan 709 days ago | link

I wouldn't go so far as to actually turn of exceptions at the compiler level. I just see no need to use them in my own code. And as for failing STL allocations: a lot of the time you realistically can't recover anyway, and where you can and want to, well, you'll have to suck it up and put a try/catch block in there. The world isn't going to suddenly end.

-----

scott_s 709 days ago | link

In C++, allocation errors don't need to throw an exception.

  Object* obj = new (nothrow) Object;
If the above allocation fails, obj will have the value NULL. It's tedious, but you could replace the default, throwing allocation with a non-throwing allocation in all places of the STL. However, various parts of the STL throw other kinds of exceptions. But, I think one could have a compromise, where you deal with exceptions from libraries you use, but you don't throw any yourself, instead using C-style error handling.

-----

cageface 709 days ago | link

If you replace the STL allocator with a non-throwing allocator how do you know that that vector you just push_back'd didn't actually allocate any new memory?

-----

james4k 709 days ago | link

Indeed, you have to roll your own data structures. This is pretty common in the game development world, where exceptions are considered to have unpredictable performance characteristics.

-----

pestaa 709 days ago | link

That is a pretty tight requirement, considering the internals of Ogre 3D engine. It is full with exception classes (some might argue it is way too overengineered.)

Are custom data structures available as open source that don't use exceptions? Or perhaps can you please name an engine that already has this?

-----

james4k 709 days ago | link

There is EASTL, though I've never used it myself. https://github.com/paulhodge/EASTL

Valve's Source SDK makes extensive use of their own data structures, however it is very specialized. For example, linked lists are allocated as growing, contiguous blocks of memory to reduce cache misses. The Doom3 source code might be worth looking at, but I'm sure it's the same story. https://github.com/TTimo/doom3.gpl

At the very least, there's a lot of great reference out there.

-----

scott_s 709 days ago | link

You don't. It will segfault.

-----

gnaritas 709 days ago | link

Many would say doing non trivial work in the constructor is bad practice anyway. Constructors should be simple, set instance vars, and be done; all necessary state should be passed in. The objects job is to be what it is, not be its own factory. Construction of complex objects is better left to factory methods, factories, or builders.

-----

rubashov 709 days ago | link

> alloc errors throw

Which usually aren't really recoverable anyway. I mean, you can also blow the stack. Then what? Just ignore those essentially unrecoverable eventualities and terminate.

-----

rumcajz 710 days ago | link

Yes. C syntax compiled with C++ compiler is a reasonable option. The question is whether it deserves being called C++ then.

-----

figglesonrails 709 days ago | link

Yes, it does, for the same reason that C++ code that is compiled with a C compiler can be called C.

-----

Roboprog 709 days ago | link

Part of the reason I dislike C++ is that I learned Borland's Object Pascal first. Knowing something else that did the same things as C++, only in a less cluttered way, was a disappointment.

Also, back in 1990, there was arguably no standard, widely distributed, C++ library, either. (which also made Object Pascal more appealing, if you didn't mind getting a language from one vendor that only ran on DOS or Windows)

C is a quite acceptable portable assembler, though :-)

-----

pmjordan 709 days ago | link

Don't get me wrong, I'd love to see a real contender for a C++ replacement. I just don't understand the attitude that because some aspect of C++ is distasteful, going back to pure C is the answer.

-----

nkurz 709 days ago | link

Then again, maybe an entirely different language, geared towards high availability, such as Erlang or Ada, might have been a better choice in the author's case.

I don't follow, but it's clear from your other comments that you know what you're talking about, so I fear I'm missing something.

His goal is for ZeroMQ/Crossroads to be part of the infrastructure, one of those reliable "3rd party libraries" he wants to avoid using himself. He wants it to be a rock solid base on which a high availability solution can be built. Likely this solution will be built in some higher level language (Perl, Python, Ruby, Java, Erlang), and he wants to insure that that language doesn't get tripped up by error handling in the his library.

My instinct is that you need to write the library in something that compiles to a shared library with a C ABI, since this is the only common denominator which the higher level languages can link to. While this is technically possible with Ada, I don't know of any common libraries that do it. And I can't see how it would work for Erlang. I think the only realistic choices are C and a subset C++ with "extern C". Maybe also Go? Do Ada and Erlang fit the bill for this in a way that I don't understand?

-----

georgemcbay 709 days ago | link

"Maybe also Go?"

Nope. At least not right now.

I love Go and Go makes it pretty easy to wrap C (and thus strengthens the argument for using C as a basis for any core libraries), but the story for consuming Go code from anything other than Go is basically non-existent right now. There isn't even a defined way to make Go shared libraries that other Go code can access yet.

-----

beothorn 710 days ago | link

This ^ If a language feature doesn't work for you, you don't need to use it. In this case not using it is writing c, but this is true to every program language. When I use a oo language I always use composition and ignore inheritance. Unfortunately you can't avoid dealing with those complexities if you use a library, but if the advantages of using a language/platform outweighs the advantages, is awlays possible to work around it.

-----

rubashov 710 days ago | link

> not using it is writing c

But that's not true. Giving up on exception handling and initialization code in constructors still leaves tons of super useful C++ to use. Generic programming, the standard library, what's left of OOP rather than crazy function pointer stuff, etc.

-----

alexchamberlain 709 days ago | link

But you can't use the standard library, because it is all based on exceptions.

-----

pmjordan 709 days ago | link

If you're happy for your program to crash on memory alloc failure, just don't handle them. It's often perfectly acceptable to do so.

-----

alexchamberlain 709 days ago | link

Errrr... what did you just say!?!

-----

pmjordan 709 days ago | link

This obviously depends on your environment. I don't recommend doing this in embedded devices or an OS kernel (this is probably why even C++ OS kernels have exceptions disabled, and no STL implementation).

But if you're just an app running in an operating system with virtual memory, the kernel's out-of-memory-killer can take you out anytime anyway, so trying to recover from every single failed memory allocation is often futile. The most likely reason to get a failed memory allocation on such a system is that you're out of address space (in 32-bit environments). If this is an issue for your app, you'll need to take higher level design decisions to cope with this fact anyway.

If you actually run out of memory (physical + swap) on such a system, you will usually not find out on allocation. Instead, allocations (mmap()/sbrk()) will succeed, but when you write to a previously untouched memory page, it will page fault, not find a backing store for it, and invoke the out-of-memory-killer. Good luck recovering from that.

Just to be clear, this doesn't absolve you from managing and conserving your memory diligently. It's just that if you wait for memory allocations to fail before reining in your program, you've probably made your user's system swap so badly it's barely usable. No cookie for you!

-----

rubashov 709 days ago | link

If the system is out of memory, and you're not in some special case huge allocation, odds are very high your program can't proceed in any useful way. You're going to have to terminate anyway. Why pretend it's a recoverable exception? It's usually of the same class as a stack overflow.

If you can't tolerate that philosophy, you might as well just give up dynamic allocation altogether and have everything in stack allocated std::array's. But you still might blow the stack.

-----

beothorn 709 days ago | link

What I meant by "not using it is writing c" is that in this case you can put it as "c vs c++", but the real issue is using or not using a language feature. I phrased it badly.

-----

simcop2387 709 days ago | link

Part of the issue with doing that is that by compiling as C++ the symbols get mangled in a different manner and can lead to areas where you have to compile nearly everything as C++. Or you have to make sure that everything that you might want to face the outside world is marked with extern "C" {}.

-----

pmjordan 709 days ago | link

This is by and large a very minor point. I typically define a macro similar to this:

  #ifdef __cplusplus
  #define CFUN extern "C"
  #else
  #define CFUN
  #endif
and mark any C-callable functions with it. Easy. The real linkage problems you'll run into with C++ are in dynamic libraries. The higher level of abstraction hides some problems that are really obvious at the C level. Fortunately, you only need to care about this if you need to support third-party plugins to your software. If that's the case, you may want to reconsider your choice of using a low-level language such as C++ or C.

-----

planetguy 709 days ago | link

This is pretty much the way I use C++; as C, except with the occasional excursion into C++ when it's more convenient.

The downside is that while I claim to be able to program in C++, my C++ idiom has nothing in common with anyone else's. For example: what the hell is an STL? How do exceptions work? I have no clue.

-----

luser001 709 days ago | link

Practical use of the STL can be learned in an afternoon. Just focus on the Big Four: string, map, vector, and unordered_map (last one is in in c++11 only).

The venerable original SGI document should take you quite a ways: http://www.sgi.com/tech/stl/table_of_contents.html

Good luck, in case you decide to do this.

-----

pmjordan 709 days ago | link

I think it's probably worth knowing the language and at least trying out all of it. Holding an opinion from an ignorant position is just blind dogma. Who knows, you might discover a delightfully useful facet.

-----

rubashov 710 days ago | link

Yeah, just don't use virtual methods or constructors on classes and his second complaint pretty much goes away. And you get a syntax and amenities that are still way nicer than C.

-----

More



Lists | RSS | Bookmarklet | Guidelines | FAQ | DMCA | News News | Feature Requests | Bugs | Y Combinator | Apply | Library

Search: