Why should I have written ZeroMQ in C, not C++
250bpm.com
250bpm.com
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.
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.
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.
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)
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.
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).
I'm 99% sure that ICC is compatible with GCC.
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.
http://pcroot.cern.ch/TaligentDocs/TaligentOnline/DocumentRo...
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!
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.
[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.
"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.
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.
>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."
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++."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.
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.
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.
Ah, Java code at my workplace.
catch(Exception e) {}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.
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."
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?
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...
try {
foo();
catch (std::exception &e) {
handle_exception ();
}
int foo () {
int rc = fx ();
if (rc != 0)
throw std::exception ("Error!");
}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.
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.
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. std::unique_ptr<Image, void (*)(Image *)>(image, deallocate_image);scoped_ptr<Image> image(allocate_image(...));
// 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.
In a static language like C++, this is a big disadvantage.
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.
Googling for ---java checked exceptions--- is illuminating.
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.
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.
In Java, you can do:
public int myfunc(int a, int b) throws IOException, InvalidGoatException {}
How would you do this in C++?
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.
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.
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.
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.
I stopped reading about halfway through as most of the code samples reinforced this.
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.
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.
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 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.
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.
See here for many solutions: http://stackoverflow.com/questions/553103/can-i-disable-exce...
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.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?
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.
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.
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 :-)
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?
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.
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.
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!
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.
#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.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.
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.
There are ways that you can use constructors, one way is to have an error-code as part of the object that the constructor can set. Another way is to use the empty-constructor/init-method pattern that he talks about and wrap it in a static method (or function) and that becomes your actual constructor. I think you can even make the constructor private which will prevent accidental calling of it.
But this is just another example of why high-reliability systems require considerably more discipline in C++ than in C, since C++ gives you more rope, it's easier to hang yourself.
There is an argument that such a limited dialect of C++ is so close to C (with a good 3rd party data-structures library) that there is no point to C++ in such situations (which seems to be the point the article is making), and I tend to go back and forth on this.
Just look at all the contortions WinRT has to go through to expose its APIs versus the clean Foundation/Cocoa layering you see in iOS/OS X for a great example.
The only single major issue in creating infrastructure in C++ is the enormous added complexity in the final library and dependencies.
While you can avoid mangling and support a "C" api by using ``extern "C"'' declarations, this mostly imposes a "c" like, no-oop API that doesn't really save much work compared to a "C" api.
You can take a .o built with gcc (g++) and link it with a .o generated by clang (clang++) without issues. Same name mangling, same everything. This means that I on my system at least don't care anymore whether the project is built with GCC or with clang and I can use either or for my own projects.
The thing though is that the C++ standard doesn't have a default ABI, so saying that it is non-standard isn't even the case, that we get any compatibility is nice at all, and I understand that complaint.
The biggest issue I've ran into is that even with the same compiler on the same platform (using MSVC 2008) setting different compiler flags generated completely different code and memory layouts for std::vector that caused me to not be able to link against a DLL. At least some consistency exists with other compilers and different flags don't necessarily change compatibility.
I don't think it is entirely fair to compare WinRT with Foundation/Cocoa. WinRT has an entirely different goal than what Foundation/Cocoa provide, and Foundation/Cocoa provide none of the same things that WinRT provides such as a object model that can be used across different programming languages fairly easily. Mac OS X has nothing that even comes close to COM.
Because it doesn't need something like COM. As is so often the case, Apple has made some radical simplifications. ObjC has its faults but the C/ObjC/ObjC++ stack covers a lot of ground with minimal mental overhead.
How does it work in Mac OS X?
And one think that I love about ZeroMQ, is that even being C++ it exposes the stuff with extern "C" and makes life easier. I wish Qt, wxWidgets were easier to expose this way (okay, there is SWIG, or some other tools luabind, etc. - but they produce additional DLL/.so files that are sometimes as big as the library they are wrapping (if not bigger)).
I cannot give specific examples, but I am pretty sure that you'll have a hard time trying to combine modules generated from different compiler vendors.
Already the first hurdle is that each vendor has its own language runtime that is only compatible its own implementation.
Second, not all languages define a binary ABI on the language specification.
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....
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.
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.
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.
Fine to disagree, but respond to the point. So far the relevant objections I've seen are: (a) you shouldn't do anything in a constructor that might fail; and (b) destructors can't fail. Weak. Is there a good counterargument? To be precise: why is the OP wrong to say that, to be strict about error handling, his C++ needs to look like the block of code that follows the sentence, "There's a new 'semi-terminated' state" in the article?
(As for most of the comments, which boil down to "OP is ignorant of basic things about C++ and programming", come on you guys, how likely is that? Who's more likely displaying ignorance here?)
And ranting about it seems like ignorance to me, but that's beside the point.
For me, checking return values is just as, if not more, tedious than catching some exceptions. Return values should be used to guide the flow of the program state (IMHO), and exceptions represent invalid states.
I have no opinion about this, but given that it's the point of the article, that's what people should be addressing.
The fact that errors are explicitly returned requires the programmer to explicitly handle them, which encourages design that considers error handling.
The evil of exceptions is they hide errors until they happen.
A fully-fledged condition system (like Smalltalk, CL) would allow for a lot of scenarios where exceptions currently give trouble. They would allow recovering the problem where it happens, retrying operations, and so on. Too bad so few languages have that.
However I don't think the right solution is to use an even more primitive language. C seems simple, but you end up doing everything yourself that would otherwise be done by the compiler, which is a lot more work, bug-prone, makes the code verbose, and generally inconsistent (unless you're very disciplined; forget about it in a team).
here's a rough, probably incorrect demo in python: https://gist.github.com/2653752
gives you nice, clean, exception-like error handling that doesn't leak to the wrong level of the call stack, and in a statically typed language the compiler can verify your error handling for you. quite similar to checked exceptions. call stack issues can be fixed with some low level C, or you could even unwind the stack with some exception trickery.
I've read the Google C++ style guide and it's basically a joke. At least it was a joke two years ago. The guide even contradicted itself in multiple places. Google bends over backwards to avoid using exceptions and the result is that C++ isn't C++ anymore.
http://gcc.gnu.org/wiki/CppConventions
I salute them on following strict coding conventions.
I can't think of any other way to handle errors apart from using return values. Methods return error codes which are handled by the caller or bubbled-up to its caller. This tends to result in lots of duplicated error handling code, and you have to either dedicate the return value to success/fail (which means you have to return non-status data some other way) or use special values to indicate failure.
The Go language (from Google) doesn't have exceptions and allows functions to return a value and an additional error return value [1]. I'd guess that this is inspired by their approach to C++ coding.
You could also extend it to support multiple non-error return values, like Python does. But this introduces problems with expressing the contract between caller and called (e.g. [1]) and I think the best way to address this might be to extend the language syntax.
I think one of the most interesting design choices in Go is that it intentionally omits conventional exceptions. It does have defer/panic/recover [2] though.
[1] http://stackoverflow.com/questions/354883/how-do-you-return-... [2] http://blog.golang.org/2010/08/defer-panic-and-recover.html
Error handling is a pain in any language. "This thing that should NEVER fail just failed, now what?" If you're not in a recoverable state, realistically, what can you do?
I know that constructors can fail, but it's pretty abnormal for that to happen. I like the way objective-c handles it. Since everything is dynamically allocated and you can just see if the object exists to find out if it works. Lots of methods have an extra NSError parameter that you can use if you need more information.
> There are only two states. Not initialised object/memory where all the bets are off and the structure can contain random data.
He can get exactly the same effect by mimicking fstream interface: use constructors and destructors as they were meant to and introduce bool operator! to check whether the object has been successfully initialized.
In this scenario, only destructor needs to know about the "fail-constructed" state. Other methods don't need to implement the state-machine; calling them on "fail-constructed" object would be undefined, exactly as in his C version (e.g., calling foo_bar without calling foo_init and checking that it succeeded)!
If you have to wonder about what a statement really might mean depending on a bunch of state that was fed to the compiler then that really spoils things.
Not that you would ship ZeroMQ code in it (it's GPL), but still.
Oh, and I love ZeroMQ, and I even love it more, that the "C" interface was the one being public, not the "C++".
Also good article. I love exceptions in higher-level languages - lisp, lua, etc. - but I prefer error codes in lower-level ones, and I consider C++ as such.
which object is he talking about here ? if foo inherits from bar, object foo is not considered a valid object till it's constructor completes. so there is no question of destroying parts of objects which were already constructed.
similarly, during object destruction, as soon as the destructor is invoked, the object is in an invalid state.
i guess there are some issues in understanding of object lifetimes and exceptions thrown by it's constructors...
Granted, exception is problematic. I don't use exception for similar reasons. However, given the nature of this project, it should have been clear that exception should not have been used in the first place. Instead, be very explicit about error handling. This is really a poor design choice. How is it the conclusion that the answer is C, rather than "C++ without exception?".
There is one language/platform[1] I'm aware of designed for this type of system, and which achieves it beyond all others: erlang.
If Uptime is important, how do you handle it when you want to update your software under C++ or C? You have to bring the service down. Erlang supports updating software while its still running.
If exception handling is important (to prevent crashes) how do you handle it when there is a bug? Erlang has supervisors that not only help you manage exceptions but keeps the service going when an unhanded exception causes things to crash.
More importantly, intrinsic to the nature of the way you write erlang code, you eliminate a great deal of the possible errors. In C code you have to check to make sure things are in the right state while running your code. In erlang, your code doesn't run (the function isn't called) if the state isn't right to handle it. It flips this problem eliminating many classes of errors. Further there is OTP support for many types of processing (like state machines) that makes writing these types of solutions much easier.
Erlang is great for making sure no undefined behavior happens. Erlang is also great (as I understand it, this I haven't gotten into) for creating unit tests that can fully exercise the code across all possibilities.
Where does erlang suck? I don't think it sucks anywhere because it has solutions for all of its own problems. For instance, single node performance of an erlang program (which runs in the BEAM VM) is going to be lower than single node performance of a C program... but in erlang you can increase capacity nearly linearly simply by adding more nodes... while this is nearly impossible with almost all C/C++ programs without spending a lot of time working on it.
But where erlang is weak is that single node performance which might be really important[2], and if it is, you can then write your performance critical bits in C.
Thus the things that do the real work can be fast and in C but the things that keep the system going and distributed (where performance of this kind is much less critical, but performance of the operational kind is paramount) can be written in erlang.
So, I think the second mistake he made, and is still making, is thinking that the project has to be written in a single language.
If you use the best language for the job, then sometimes that should mean using two languages right?
Erlang is not a hipster language. No hipsters use erlang. It is 25 years old and boring and %99.9999999 uptime. Its ugly. Its not hot and fresh and new. But it is the right language for the job, when you've got a system that need to run across cores or nodes, or have very high uptime.
And really, it doesn't take but a couple weeks of learning. Then the syntax will be gorgeous and you'll be a better programmer for it.
[1] I say "I'm aware of" for a reason. There may be others as well suited when this is the primary goal, possibly even better suited. I don't know all languages. But I do have a pretty good survey started in the days when new languages were as common as YC batches and much more experimental than they are now. My point isn't to bash other languages but to promote one that isn't understood correctly by many people.
[2] I think this is the case a lot less often than people think. People benchmark things on a single node even when they know they are going to build a cluster of machines to run them, and then pick them based on this. Redis is really fast. Is redis distributed? Riak has a fully distributed in memory database (which means 10 32GB nodes means you can store 320GB in RAM if you want, rather than have 10 nodes all storing the same data in a cache, and thus much less data cached for the same number of machine with something like Redis.
ZeroMQ is software that is more at the level of the Erlang VM itself. It's not an application, it's infrastructure. You wouldn't say that the Erlang VM should be written in Erlang.
This is not to take anything away from Erlang, which is a very interesting system.
MUDs routinely did this back in the day. Here's a README excerpt from Erwin Andreasen's widely used patch:
"Basically, for each playing descriptor, it saves the descriptor number, the player's original name as well as the host name (so we don't have to find that again). Then, it closes fpReserve, and exec's the MUD again. As exec preserves open file descriptors, players do not lose link. The MUD is executed with some extra parameters, so the new copy knows that it should reload some player files. It does that, and puts in the players at their old places."
This is a very novice thing to say, for any language. I've found it takes me about 3-6 months of regular use to start seeing the warts of a language (and a few more to learn how to work around them).
Erlang seems pretty boss (I haven't used it myself) but this post reads like "just buy a Mac and everything will be easier!!!" Unfortunately that doesn't take into account the user's experience and needs. Good luck switching to a tiling window manager on a Mac.
http://journal.dedasys.com/2007/09/22/erlang
It's from nearly 5 years ago, so they may have fixed some of those things.
Nginx has a hot update feature and is entirely written in C.
Further, Erlang makes no attempt to avoid undefined behavior. It provides mechanisms to restart pieces in a usable state to bring the system back into line rather than try to avoid or fix incorrect states.
Please don't post opinions about languages you have never done real work in. Your weekend project doesn't count.
Then hot code reloading, supervisor structure, isolation of faults, actor model all sort of fell out of that.
And as Joe Armstrong also said in respect to single node performance is that "there is no free lunch". In other words at some point you'll have to sacrifice some performance if you want fault isolation.
Specifically, Erlang has a good (and professionally supported, IIRC) port of Haskell's QuickCheck for exactly this sort of testing.
If uptime is not the driving feature of the project. It will be very hard to add it later as the language chosen and the libraries have to have also been built with uptime in mind.
Testing based on OTP service trees also makes the verification fit the possible transitions in a much closer way than trying to cover all the branches in an OO app.
So yes, I'd say it's actually quite a good response to an "exception handling all over the place" problem. You simply don't do that. Instead your exceptions finish at the FSM level and progress you to one of the error states on your diagram. You know exactly what state you're in at that point so the behaviour is pretty well defined at almost every step.
However zeromq is a library that was supposed to be portable, rather than a daemon itself, so from that perspective, it's a very bad fit for ZMQ.
Lost messages are a fact of life really - the same will happen with any other system - either you persist the message and ack the reception on every stage or you risk dropping the queue.
On the other hand, there's no guarantee the message arrived in the first place (especially when using multiple nodes) unless you use ack patterns.
Here is the quote:
> First, it's important to take into account that ZeroMQ was intended to be a piece of infrastructure with continuous uptime
So it is about uptime, that's the supposed goal of someone using ZeroMQ. C++ has undefined behavior and this, the author claims, can lead to more crashes and reduce uptime.
Then nirvana suggested that if uptime is needed, there is a better language and platform designed for it.
Poor attempts at proselytization like this do more to harm Erlang than help.