>The more C++ you know the worse you become at working with others. First, because your particular dialects of C++ tend to isolate you.
So why is using something non-standard and non-universal like <czmq.h> not perceived as a "dialect" of C?[1] Any non-trivial source codebase beyond "printf("hello world")" will be a "dialect" of the programmer(s). When looking at the C source files of Linux kernel, Redis, and SQLite, etc, the syntax patterns, helper macros, string manipulations, etc do not look the same.
Also the author's example of,
for (i = List.begin (); i != List.end (); ++i)
cout << *i << " ";
The C++ loop is multithread safe. The C version is not. For the zlist_next() to work, the "list" data structure needs to maintain mutable state in between subsequent calls. (Think of how something like strtok() works by mutating the string).
[1]possibly because Pieter Hintjens is the programmer & CEO behind ZeroMQ and czmq.h. Therefore, it doesn't feel like a dialect to him?
CZMQ is a library of around 30K lines of code. The Scalable C book is in many ways a guide to using that library in real projects.
Of course every programmer develops their own dialects, just as every writer has their own voice. C is however a small language and if you stay away from weird macro magic, any well-written C code is mutually intelligible to other C programmers. Whereas with C++, dialects can have so little overlap they are not mutually intelligible.
FWIW neither of the fragments is safe if you are sharing state between threads. In practice we do not share objects between threads, and each object holds its own state, and thus our C code is 100% thread safe and reentrant.
>The Scalable C book is in many ways a guide to using that library in real projects.
I'm not criticizing the book or czmq. I'm sure it's a fine library. I just found your characterizations of C++ to be strange.
For example: ">, any well-written C code is mutually intelligible to other C programmers."
If you're going to qualify C code with "well-written" to help make your point then can't the same qualification be applied to C++? If so, it means you believe that there is "well-written C++ code" that is simultaneously unintelligible to other programmers. In your opinion, what would be an example of that? (E.g. If you can point to github of unintelligible but well-written C++ source code.)
And your other statement: >, because your particular dialects of C++ tend to isolate you.
I don't know if CryEngine and Unreal game engines are well-written C++ but they seem attract developers. There's also the scuba diving app[1] written in C++ that also has Linus (who you already know hates C++) modifying the source code. I contend there are bigger factors than C++ dialects causing isolation.
The C++ ABI may be more isolating than the C ABI because of C++ name mangling incompatibilities. But I don't see how the C++ dialects (of well-written code) are isolating.
Since you ask, I'll admit it: I enjoy trolling C++ users because the language has so often thought of itself as superior to C. Beating on the language always gets lots of discussion going, which is fun. I've nothing against the language, or any other language, as such. Good tools, in the hands of good developers.
I find it the other way around. It's usually C hackers who tend to describe C++ as the devil incarnate. C++ developers tend to be more pragmatic - we'd happily write in C if necessary, we just find it limited. And since C++ is almost a superset of C, there's rarely any need to use it.
Thank you your honesty. Now, C++ developers can stop trying to have a productive conversation with yoir new generation of hipster C programmers crafting smal-batch artisinal bytes, and get back to getting work done.
Given the sheer breadth of approaches and features in C++, any one codebase will only touch some of it.
Inevitably this means that if you get used to C++ in one project, you will have more difficulty moving to projects using a different subset.
From experience, both unreal and cryengine are perfect examples of Pieter's point. They're large codebase with archaeological layers of many different c++ styles and paradigms.
Please consider the precise argument I was responding to.
Pieter's point is a stronger statement than just the existence of "different styles". (Every significant codebase by multiple people has multiple styles -- e.g. see Linux distribution.) He was stating that there was a cause and effect such that the different styles ("dialects") cause "isolation".
If so, it means Apple's Swift compiler written in C++ on github[1] should be getting near zero forks and pull requests. In his theory, Chris Lattner and his dialect of C++ should be "isolated". It's also possibly unintelligible C++ (although I haven't looked at it yet).
If it was just one of those throwaway sentences to troll people, then fine. My first impression was that he wanted his programming guide to be taken seriously.
I enjoy reading C++ criticisms. The criticisms in the C++ FQA by Yossi Kreinin has interesting points. I'd prefer it if the faults of C++ were accompanied by evidence.
Yes, like I said, speaking from experience, different C++ code styles and paradigms absolutely do cause isolation. Having to climb yet another mountain in order to be effective on a different codebase in the same language means people don't do it, so they become experts in a single codebase -- isolated.
I've never really got the "C++ dialects" argument so popular with C enthusiasts. Sure, C++ is several order of magnitudes more complex than C, and this complexity breeds different dialects. But for an experienced C++ programmer, that's a stylistic problem (you generally end up disliking the dialect of about half of the libraries you use), not an intelligibility problem.
I've read many libraries, both C and C++, and I've never found C++ libraries particularly hard-to-read. There are a few libraries rife with deep template magic (Boost.MPL et al.) but they are hardly the rule.
The real problem with C++, is that truly competent developers are hard to find. I've worked with and interviewed countless C++ developers. Very few used anything from C++11 (except for auto perhaps), and most are essentially program C++ as C with Java-style classes bolted on top. Templates are few and far between, and manual memory management abounds.
So these incompetent C++ developers you hear about in Linus' frequent rants? They're mainly C developers who learned some C++.
Good C++ developers write cleaner code that's easier to read compared to C, but it takes more time to train one than it takes to train a C developers. But if you only care about fast developer training, Java, or even better, Go are much better choices than C.
As a Java developer, I have found the source code of well-designed C++ libraries (like libtorrent) actually a pleasure to read. While C libraries need lots of 'macro and typedef' hunting. Doable but a chore.
Unfortunately, the Boost libraries are really arcane - template magic that gives me a headache. So, I guess I fall into 'incompetent C++ developer', but that is not a surprise as it hasn't been my working language for some years now.
If one particular language suffers so much from all its programmers being incompetent, it should raise the question of what about the language makes it so hard. You can't reasonably dispute that C++ is a gigantic language full of optional features. To be competent with C++, you really have to learn which features to use or not. It's too bad that not everybody agrees on which features to use! Blaming outgroup C developers for everything is a cop-out.
Oh, if it was only so easy! There are many other languages which abound with incompetent programmers. Even an atrociously simple languages had many incompetent programmers over the years. On the other hand, there don't seem to be a lot of incompetent Haskell programmers.
It's usually practical reasons which determine the influx of newbies with no previous experience and little incentive to learn to a language. Popularity-wise, C++ was the Java of the 90s, and many programmers came during that time and don't want to learn anything new.
Many other programmers are essentially not willing to learn C++ features beyond the features they're already comfortable with.
"Which features not to use" is another myth propagated by professional C++-bashers. There are very few features in C++ that you shouldn't use, and most of them are officially deprecated parts of the standard library. The real problem is developers who refuse to learn templates, because "void pointers are simpler", or reject std::array, because it's fancy C+11 stuff and writing "char myArray[100]" is much shorter than "std:array<char, 100> myArray;". They then go on to forget std::array ever exist, and complain that vector is too inefficient, so they're going to use C arrays all the way.
If you're willing to use all these newfangled features like closures, initializer lists, 1-part for or constexprs, its' very hard to shoot yourself in the foot. It's very easy to shoot yourself in the foot by using raw C arrays or manual malloc/free though.
The C loop is not multithread safe because of this right in "why this book exists":
> The standard approach for concurrency is POSIX threads that share their state. This is complex and fragile. We know how to do this better, using message passing between threads.
In other words, the code he's presenting is not expected to need to be thread safe because the structures are not expected to be accessed from more than one thread.
I'm not "expecting" his example code to be multithread safe either. (By default, I expect all code to not be multithread safe unless it is explicitly engineered to be so.)
I'm pointing out that the code does not work the same way under the covers. The author would know there's mutable state hidden in that "list" object but I don't think it would be obvious to others at first glance. If it was, programmers wouldn't get bitten by bugs because of misunderstanding ctime() and strtok() in single-threaded programs. Likewise, the zlist_next() can also be inadvertently misused to cause bugs in single-threaded programs.
If the author knows how czmq internally works and how it's supposed to be used (don't multi-thread or misuse in single-thread), but outsiders haven't grokked those guidelines yet, can that coding paradigm be labeled as a "dialect"? That's the interesting philosophical question to me. Words like "dialect", "explicit", "syntactic tricks" are relative to the programmer and I think the author's code examples illustrate those subjective biases rather than advise us of objective truth.
I think there is a fundamental difference there in that as you say: You by default expect all code to not be multithread safe. That's a default assumption for most C/C++ developers when encountering a class or library they don't know because it is very common. To the extent that quite a few people argue that libraries should not take extra effort to be thread safe, as is the case here. Besides, it is obvious from the API in this case that there is hidden state to all but extreme beginners.
I think his argument regarding dialects in C++ is sound to an extent. C is quite boring and settled when it comes to structure and interop at the API level. APIs may vary wildly, but e.g. <libraryidentifier>_<classname>_<methodname> or <libraryidentifier>_<functionname> with pointer to an "object" or struct holding generic state is very well established, for example. A lot of the simplicity there comes from the simplicity of the language. The internals of how the code is written varies more, and things like build systems etc. varies wildly. But at the end of it you are usually left with some headers and a bunch of exposed symbols that follows a relatively limited set of styles.
C++ has far trickier issues, in that you are dealing with people who wrap everything in templates; people who try to use functors all over the place; people who want exception safety everywhere; people who ban exceptions in their codebases; smart pointers vs. bare pointers etc. C library code can usually interoperate. C++ code written in different "cultures" can sometimes be like trying to make LEGO and PlayMobil fit toys fit together. Many C++ developers have gone through "phases" - for my own part, before I mostly ditched C++ I'd written codes in styles that appeared totally alien to each other...
I would add that this goes deeper than just coding styles. My current project relies on a number of widely-used C++ libraries (Qt5, OpenCV, ITK, VTK and a homegrown framework) and each one of them redefines basic data types (std::string, QString, cv::String).
I could go on about how none of them even acknowledges the existence of <cstdint>. I'm sure there are good excuses why each individual library decides to do this (lack of common ABI, badly designed std::string), but the end result is a heap of forced runtime - and cognitive - overhead.
This is pretty sad considering that C++ is advertised as a language that doesn't add unnecessary runtime overhead.
Qt and VTK predates C++98 significantly. OpenCV is slightly newer, but it took a long time for people to get comfortable relying on it, and I don't know whether or not it was based on older, pre-existing code.
But it's probably going to remain a drag on C++ for another couple of decades...
No, it isn't "simply age." I know projects started in 2014 that defined their own string types since there are a lot of things that std::string doesn't support. For example, you can't turn a C string into a std::string without copying. There is basically no usable unicode support in std::string. Copies of std::string use copy-on-write, which may create hidden race conditions when sharing data between threads. If you want to fix those problems, you need your own string type.
> you can't turn a C string into a std::string without copying.
C and C++ use value semantics by default. std::string is a value., a char* is a reference, so this shouldn't be surprising. If you want an immutable, zero-copy, drop-in replacement for std::string, use boost::string_ref (a variant of which will soon be standard). It'll give you a memory unsafe (just like a char\) reference to a C string or* a std::string. Want to wrap a C string safely? Use it in combination with unique_ptr or shared_ptr<char> (for exception safety).
> Copies of std::string use copy-on-write,
Actually that's explicitly forbidden by the standard, since C++11. The interface for std::string always assumed mutation, which made COW really inefficient.
> which may create hidden race conditions when sharing data between threads
The COW versions of std::string, that I'm aware of at least, were always thread-safe.
Thank you for the correction about std::string no longer being copy-on-write. I'm glad they changed this in C++11. string_ref also sounds like a step in the right direction, assuming it is well-implemented.
Two things to note. std::string is pretty much barred from being CoW as of C++11. QString is CoW, but as of Qt4, cross-thread copies are safe to modify just like normal objects.
Because if you "copy" a string and the copy really is just a reference to some shared data structure, changing the shared data structure later in another thread without memory barriers or other synchronization causes undefined behavior. Remember that the shared data structure will need a reference count to be updated whenever a reference gets deleted.
On the other hand, std::string already existed back then, but they still chose to reimplement it.
This points to a more systemic problem in the C++ ecosystem. Has C++ evolved to the point where, say, a new library written today would not have to redefine std::string?
Not it didn't. The work on Qt standard on 1991, according to Wikipedia. The work on STL only started in 1994 and became a standard with C++98, and even then it took time for compilers to get decent standard support. So every library needed its own string implementation. New libraries generally use C++ string, unless they're have very strong opinions about performance that trump interoperability (e.g. Facebook's folly).
But seriously, isn't that hypocrite to blame old C++ libraries for implementing their own string class, and then go espousing C? C of all languages - the language whose spartan standard library includes almost nothing (including usable string processing code, if you care even slightly about securing your code from memory errors), and each respectable C project ends up implementing their own lists, queues, maps, stacks and of course strings.
The standard was finalized in 1998 but the implementations on a wide variety of necessary platforms didn't happen until later. Qt's QString predated 1998.
It existed, but if you wanted interoperate with your older code, or wanted to run on various platforms, it often would not be available, or bugfree enough or fast enough. It was well after 2000 before everyone started trusting that they'd have a reasonably conformant C++98 environment. You'd still find people start new std::string alternatives up until then, for seemingly good reasons. Some of them valid, some of hem not so much.
Today I don't think anyone would redefine std::string, but I haven't written much C++ for years.
It seems you are blaming C++ as a language for the design of the libraries you are using.
I've also never seen a program actually be slow due to string manipulation (excluding grep). My guess is that if you profile, it will turn out that your performance is getting away from you in places you don't expect.
> It seems you are blaming C++ as a language for the design of the libraries you are using.
Precisely. In the same way that Java is criticized for proliferating the IAbstractFactoryVisitor design anti-pattern, C++ should be criticized for its weak standard library which causes an overproliferation of mutually-incompatible reimplementations of basic functionality.
As to whether this is or is not a performance issue depends on the specifics of the application and the underlying hardware.
This is besides the point to this discussion. Why don't C# libraries reimplement System.String like C++ libraries reimplement std::string? That's the real question here.
Because when C++ appeared, its standard library was basically C.
Then you had the early adopters that were basically the same developers that were still writing C with lots of inline Assembly.
So each developer, or team, had to write their own library, because they knew better.
It took all the way up to ANSI C++ (1998) to finally have a proper minimal library. Minimal, because C++ needed to cater to the C mentality that the OS is the runtime.
Then you had almost a decade until the C++ vendors were able to catch up the standard.
Mind you that in the mid-90's there were still quite a few C compilers that were K&R only.
This is why when Java appeared, many of us C++ devs that cared also about safety, jumped into it. As it provided a more portable experience.
I was actually curious if this is true, and it appears that Unreal Engine 4 reimplements std::string (FString) [1] I think this qualifies as a modern library.
My yardstick for "pretty solid" is actually meaningful Unicode support, so I will reserve judgement as to whether the C++11 STL qualifies.
Unreal has been evolving for over 15 years and the engine does lots of preprocessing as well as being built for interactive C++, so I don't think it is the best example.
I'm also curious why. I wonder if any of the original designers ever shared any insight into their design process.
As for what is wrong with it, multiple things. My personal pet peeves are:
1. the lack of formatting or interpolation support (i.e. QString::number(3.14, 'f', 2) or QString("Hello, %d!").arg("world")
2. the utter lack of Unicode support
I've seen other people complain about how it interacts with <algorithm>, inefficient support for common operations (join, split, copy) and mutability. I don't particularly care about these, given that its lack of Unicode support pretty much relegates it to a glorified std::vector<char> implementation for me.
Yes, you can read "The Design and Evolution of C++" and the "The Annotated C++ Reference Manual" aka C++ ARM.
There you will understand why C++ is the way it is, when you are trying to sell a safer language with higher level abstractions to hardcore C developers at AT&T, with zero friction with existing C toolchains.
Many like me hate the C underpinnings of C++, but if it wasn't for them, most likely no C vendor would ever have adopted it.
I'm tired of people blaming the bad things in C++ on C. Most of the bad things in C++ came from C++ itself, not from anywhere else. The Modula-3 type system with its inheritance anti-patterns can't be found anywhere in C. "Guru meditation" template error messages can't be found anywhere in C, nor do C programs typically take hours to compile. In C, functions and types that are "static" to a file don't appear in header files... C++ "improved" that so that private types and functions must appear in header files, greatly extending compile times. Global constructors are not in C... you know what order your program will run in when using C.
The set of things in C that were "fixed" by C++ is very small, possibly even the empty set for reasonably modern dialects of C. For example, having the compiler implicitly declare functions as returning "int" was on its way out with or without C++.
Probably the worst thing about C was all the implicit type conversions-- between long and int, and char* and boolean. Of course C++ didn't fix this. They even made it worse by declaring that any C++ constructor that took one argument would be a candidate for implicit type conversion. So if you declared Foo::Foo(int i), in the future any time you have an int the compiler would consider promoting it to a Foo. Later they realized their mistake and added the "explicit" keyword so C++ gurus can avoid this error most of the time (when they remember).
If you cared to follow my posts you would see that I would rather be discussing about Modula-3 and Ada than C or C++.
Yes the blame is 100% on C compatibility.
There is an interview that I can gladly search for you, where Bjarne says he was dismayed the lack of knowledge about other languages and he had to do his thesis in C,swering never to use it again. So when he joined AT&T, he started right away to create his improved C version, which had to be copy-paste compatible with it.
Had C++ had proper modules, it wouldn't work with C linkers.
Those implicit conversions were added to make C++ data types fit into the way C developers were used to write their code.
I can assure you than I worked on a C project in 2000 that took around one hour per platform to compile.
As for enjoying C++, I rather programm in memory safe languages and since Oberon I am big fan of GC enabled systems programming languages.
But C++ was my escape from C, when Turbo Pascal started fading away. Compared with Turbo Pascal, C always seemed a meh language, only thing going for it was better portability as the Pascal dialects.
So, even with its inherited C defects, C++ does provide the tools for the security conscious developers, if one does decide to use them.
Of course, ideally, both should be replaced, but it will take ages until it becomes a reality.
Blaming someone else for your own poor design decisions is cowardly and irresponsible. Better languages than C++ can easily be built on top of C. For example, Objective C is a better language than C++, also built on top of C.
Implicit type conversions could have been eliminated by having a "use strict" mode for new files, as was done in Perl 5. C++ didn't do this because the designers didn't see implicit type conversions as a problem. In fact they dramatically expanded the number of types that could be implicitly converted to include all user-defined types, as I have just described. But I guess the evil C designers somehow forced them to do that? At what point do you start taking responsibility for your own mistakes? It's sounding a lot like "never".
By the way, C++ could have had a module system too. Objective C does, and people are talking about adding one to C++. But then again, Objective C had good designers, not "I just work here" type people who blamed all their problems on the previous engineer.
> For example, Objective C is a better language than C++, also built on top of C.
Somehow I cannot agree that a language that has [] and @ everywhere, relies on the programmer to manually write retain/release, uses dynamic typing and keeps all C defects, has a better design.
Remember Objective-C didn't had ARC in 1983.
> But I guess the evil C designers somehow forced them to do that? At what point do you start taking responsibility for your own mistakes? It's sounding a lot like "never".
Yes, when 100% compatibility with existing code is a design goal.
> Objective-C does, and people are talking about adding one to C++.
Objective-C modules is a work from Apple, relies on clang linker and wasn't part of the original language design.
Likewise, the upcoming C++ modules require the implementation of new linkers.
Both don't work with the UNIX linker model that Bjarne had to use.
Just the C++ compiler was new. The linker had to be the system linker already in place. This is how name mangling came to exist.
Languages that use modules and have their own linkers don't rely on name mangling.
There are lot of "roads not taken" that would have been better than what C++ actually did. Clearly Bjarne could have implemented his own linker-- if Go, Rust, Objective C, etc. etc. can do it in a year or two, then Bjarne could have-- he's a big boy, after all.
Even without rewriting the linker, you could create an informal module system the same way the linux kernel does-- by having well-defined and cleanly separated interfaces between modules which got linked into .a (library archive) files. Of course C++ failed to do this, either at the (nonexistent) module level, or even at the library level. C++ ABIs are a complete mess even to this very day-- it is effectively impossible for anyone but hardcore C++ gurus to know what changes to a class will break library compatibility. The best solution is to wrap your C++ API in a C API. Because C was a better thought-out and designed language, and can support stable APIs, whereas C++ can't.
> There are lot of "roads not taken" that would have been better than what C++ actually did. Clearly Bjarne could have implemented his own linker-- if Go, Rust, Objective C, etc. etc. can do it in a year or two, then Bjarne could have-- he's a big boy, after all.
Then he would have failed to have zero friction with the tools being used at AT&T and no C developers would have "incrementally" adopted C with Classes, as C++ was known back in its early days.
> Because C was a better thought-out and designed language, and can support stable APIs, whereas C++ can't.
C++ was designed to support copy-paste from C code and use the same linkers.
Everything that C can do, C++ can do exactly the same way.
It's not blaming someone else. Bjarne worked at AT&T Bell Labs, and once C++ caught management's eye and became more than his own personal project, 100% compatibility with C++ was a business requirement. Bjarne ended down expanding a huge effort to bring that down to about 99% compatibility, to avoid some of C's most insidious warts such as prototype-less functions, but compatibility was still a goal.
That goal included semantic compatibility (C++ constructs shouldn't feel alien to C) and performance equivalence (C++ abstractions should have the same performance cost as C).
That's why Objective C's good(?) design of strapping Smalltalk's syntax and semantics to C inside square brackets was never an option. Objective C containers not supporting C primitives would have never made it.
And let's not forget that Objective C uses dynamic dispatch and must box primitive types, so it could never claim to be on par with C performance-wise.
You're saying directly, C-level style and compatibility at a language-level in early C++ couldn't negatively impact its safety or maintainability vs Modula-2/3, etc? I highly doubt that given most crashes early on were C++ being done in a C-like style, using C runtimes, or calling C OS functions. The C++ style modernists here advocate greatly reduces problems outside compile times.
So, I'm a C++ opponent but it's still obvious C dragged it down a lot. Stroustrup's writings on its origins and why he made specific tradeoffs shows it's a fact. There were often more ideal stuff, from high-level perspective, that wouldn't fit with C, would break legacy, or would take a performance hit C developers would reject. None was allowed as C developer adoption was main goal.
We failed to convince them to use lots of other low-level languages, too. C developers are picky. So, their foundation of quicksand remains a the bottom.
Please think carefully about what you're saying and try to avoid making unfounded generalizations about large groups of people ("C developers are picky" etc.) People were willing to accept a lot of performance compromises to use C++. For example, C++ streams are at least 3x slower than stdio to this day. But they were still widely adopted. Similarly, exceptions caused a performance hit, but were widely used for a while.
There isn't a single "modernist" style in C++, there are dozens of permutations (exceptions vs. no exceptions, boost vs. no boost, quasi-functional vs. C-like).
I was a C++ developer for 10 years. More than half of the really difficult problems we encountered were C++-only, relating to constructs that didn't exist or were simpler in C. The reality is that C++ just has much more undefined behavior than C, which tends to make it less safe and less secure than that language. And some of the most exploitable software out there like Adobe Flash is written in the oh-so-safe C++ rather than the evil old C.
"Please think carefully about what you're saying and try to avoid making unfounded generalizations about large groups of people ("C developers are picky" etc.) "
I did. Over a decade of research, empirical comparisons, non-C-like alternatives to C, C-like alternatives to C, safer subsets of C... literally everything attempted failed to get a huge chunk of C people off C's problems. Even stuff that had most of C's advantages with less of its disadvantages. Tiniest tweaks didn't work. Whereas people on other languages and niches were more likely to experiment and try all kinds of languages for projects. So, I think it's clear the C crowd has some strong, emotional attachment to their language and way of thinking.
So, to get adoption, C++ had to build on that nonsense we were trying to replace. It had to merge very contradictory philosophies. Without the C part, the job would've been easier. We know that's true because it was in a number of safe, system and application languages. Anything with C or C-style at foundation has problems.
Think carefully about what you just wrote and ask yourself if you're being honest, or just shifting blame for your own failures on to "them." The people behind Objective C, Rust and Go aren't sitting on their hands because the oppression of "them." They're trying to make things better for authors of low-level software.
The cult of shifting blame on to other people for C++'s failures needs to end. The language sucks. It's bad and you should feel bad. Period.
I agree with you that C++ streams are rather horrible. This is probably the oldest part of the standard library and the original design predated both templates and exceptions, so it had to do with virtual functions and error states. I wish we could have better library in the standard. If I needed performance, I would rather use a third party I/O library over stdio, which is a security nightmare.
> There isn't a single "modernist" style in C++, there are dozens of permutations
All complex languages have several styles, so it's not just a feature of C. Take a look at Scala (mutable vs immutable objects), Haskell (recursion vs. folding, lambdas vs. point-free) or even Javascript (callback-hell vs. promises vs. generators vs. async/await) and it's not so different than C++. If you want one style to rule them all, there's always Go.
> More than half of the really difficult problems we encountered were C++-only, relating to constructs that didn't exist or were simpler in C.
Yes, constructs like classes or templates don't exist in C, but their warts are generally born out of the requirement for C compatibility.
Excruciating long compile time? Due to compatibility with C's preprocessor-based "module system".
Private variables and methods have to appear in header files? Again, wouldn't be an issue if not for the mandated module system inherited from C.
Weird template errors? Compatibility with C types made introducing constraints is quite hard (that's one of the reasons Concepts are languishing in the standards committee I guess).
> And some of the most exploitable software out there like Adobe Flash is written in the oh-so-safe C++ rather than the evil old C.
This software is more likely written in C with a sprinkle of C++ classes, lots of manual memory managements and, raw C arrays, C library calls and so on. My experience might not be representative enough, but the developers who tend to write this kind of malloc-happy code are the same developers who hate templates, exceptions and RAAI. You might as well call them C developers in a sheepskin.
I never said C++ streams were "horrible", just that they were slower than stdio and still got adopted. Which kind of negates the idea that among C++ "designers" (I use that term loosely) that you have to make all sorts of terrible semantic compromises to save a tiny bit of performance.
I don't think stdio is "a security nightmare" either (I assume this canard is based on the fact that it still technically includes "gets" or some other such function that nobody has actually used in new code in 3 decades.)
C++ is a uniquely schizophrenic language because you can't even use libraries developed in one style with code developed in another style. Try throwing an exception when -fno-exceptions is enabled in a google code base. Try passing code using a QString to code that wants a std::wstring. Tee hee. You can't say this about Javascript, Haskell, etc. and frankly your comparisons are vapid.
And the C++ sheep keep bleating "it's C's fault, baaaaa" even after I gave the example of Objective C, which has modules and avoids C++ template hell. You even non-ironically blamed C for the C++ concepts feature "languishing in the standards committee." Can you not see that this is the equivalent of "thanks, Obama" whenever something goes wrong halfway across the world? What the hell does C have to do with templates or concepts?
Yes, I'm sure evil C programmers are responsible for all crappy and insecure C++ software. They're in league with the Zionist media conspiracy to smear C++'s good name. We must root out these double agents wherever we find them! Onward C++ soldiers. Never question the decisions of your leaders, which are made for excellent reasons and always make complete sense.
OK, so C++ came out in 1984, and it only took them 30 years to add a way to bypass the implicit conversions they added to the language. Just once, I'd like to hear a C++ advocate say "we made a mistake." Of course it won't happen, so it's time to deny, obfuscate, and redirect blame.
Hey, C came earlier and strcat is still in the standard, as well as non-prototyped function calls. Someone told me FORTRAN still maintains backward compatibility with arithmetic ifs, and it's been like, what, 60 years? Strange that.
You are comparing apples and oranges. strcat is a library function which is easy to avoid, whereas the implicit type conversion behavior is part of the core C++ language and very difficult to escape. Non-prototyped function calls are there, but they trigger warnings on any compiler from the last 3 decades. Anyway, thanks for playing the "deny, obfuscate, shift blame for broken C++ design decisions" game.
Bjarne has been very open over the years about mistakes he thinks affected parts of c++'s development. There is no perfect language but c++ does keep trying to improve.
Libraries that are not designed from the ground up to be thread safe almost certainly aren't so that's the safe assumption. (And even plenty of libraries that are designed to be thread safe aren't because it is hard to do this right.)
> plenty of libraries that are designed to be thread safe aren't because it is hard to do this right.
Is it? Are we talking about fully thread-safe or only being reentrant?
For the most part, being reentrant is sufficient -- i think there are few cases where it makes sense to be really thread-safe (i.e., lock mutexes on every access as opposed to let the client do the locking as needed) since that forbids composability.
Making a function reentrant is mostly very easy. It's not trivial to assert that external dependencies are also reentrant, but most libraries don't have external dependencies.
Reentrant is more general than thread-safe. Thread-safe can be achieved with hacks specific to a threading model: thread-specific variables, locks and so on.
Reentrant is just about absolute. As in, pretty much, "you can stick this routine into an interrupt context on a randomly selected microcontroller and everything is cool".
Look at this particular zlist example. We can make it thread-safe with thread-specific variable for the iteration cursor. Yet, the function will not still support recursion, or even nested iteration of the list. A reentrant function will.
Making a function reentrant is not easy, if it has arguments which are pointers to various objects.
Only functions that receive everything by value and don't refer to anything global are easily made reentrant.
(Of course, the compiled code for all functions refers to global things: registers, such as the stack pointer. So reetrancy isn't absolute; it depends on machine contexts being correctly saved and restored by all the responsible code, like trap handler entry points. The compiler also can't do stupid things like use static buffers for passing structures (very early C compilers did that) so the source code only looks reentrant.)
For example, a fully thread-safe binary tree would be a tree data structure which allows concurrent insert operations on the same tree instance.
Whereas a reentrant binary tree allows concurrent inserts, but not on the same tree instance.
So, your typical reentrant function takes care not to depend on or modify global state. But it might read from and/or write to an implementation-specific datastructure.
Run a "zgrep -l reentrant /usr/share/man/man3/*.gz" which will convince you that's the usual notion. In particular, look at the manpage of qsort / qsort_r.
You can see that making a function reentrant means typically converting static data dependencies into dynamic ones. The straightforward and very often simple approach is using function arguments (plus a contract that the arguments are not shared between "threads").
Making dependencies thread-local is another approach, and "errno" is a good example for that.
No kidding! For instance, the memcpy function is reentrant: but only as long as two contexts don't call it at the same time, and pass pointers to the same memory. It is not unconditionally reentrant. Some functions are; those that don't take pointers to anything as arguments. I covered that in my comment.
An absolutely reentrant function could use memcpy internally (for instance to copy one object stack on its stack to another), so in other words, memcpy doesn't have bad properties that prevent it from being integrated into fully reentrant situation.
An insert function that doesn't handle two threads inserting into the same tree isn't reentrant when called that way. It execution cannot be suspended and re-entered, except with different objects.
The posix _r doesn't mean "absolutely reentrant"; it just means "this function doesn't do stupid things under the hood with globals that would prevent it from being used in reentrant code".
Why do you insist? Please argue with the POSIX people writing these manpages (and correct Wikipedia) if you think that they are all wrong and your nomenclature is the right one.
Reentrancy is logically weaker than thread safety.
For instance, malloc is thread safe in POSIX, right?
Yet, you cannot call it from a signal handler, at least not an asynchronous one. This is because it isn't reentrant.
(POSIX would call it "async signal safe", but that's totally POSIX-specific terminology. POSIX uses some terms in POSIX specific ways, unsurprisingly. And "async signal safe" is something less than reentrant because functions can be made async signal safe by manipulating signal masks to avoid being re-entered.)
I'm not the one who made this discussion about POSIX; my original comment wasn't about how POSIX uses "reentrant" (and I don't care, for that matter).
> Reentrancy is logically weaker than thread safety.
It isn't; neither is an implication of the other. If you mistrust manpages, lookup the definition on Wikipedia.
But in many contexts "reentrant" is the better "thread-safe", as in "what you actually want" (give up mandatory locks to get scalability, composability...).
Do you have an example of a function inherently reentrant (pure function), or a function or object used in a reentrant manner, that isn't also thread-safe?
Wikipedia's first paragraph on Reentrancy completely agrees with me; I'm not inclined to change a word.
POSIX doesn't define "reentrancy" or "reentrant", though it uses those terms. They are not defined in the documents it includes by normative reference. ISO C is one of those references and also throws the word around. ISO C refers to a document called ISO 2382, whose Part 1 gives a technical vocabulary; it might be defined there.
By POSIX, of course I don't mean the man pages on a Linux system, but the actual specification. I refer the online version from time to time, specifically this one: http://pubs.opengroup.org/onlinepubs/9699919799/
> The C++ loop is multithread safe. The C version is not.
Neither of these loops are multi-thread safe. That's assuming that's an std::list, and even if it isn't you can't implement that C++ API in a fast and thread safe manner.
Yes, the ZMQ list is awkward (why did they put the "cursor" or the iterator inside the list object)? But that's not an inherent property of the C language.
If you want an example of how to implement a linked list in C elegantly and efficiently, look at the intrusive linked list implementation in the Linux kernel (also available separately in the CCAN project).
The CZMQ list embeds the cursor because (a) it is sufficient for all cases we've encountered and (b) it makes calling code simpler. This iteration pattern is widely used in CZMQ and Scalable C applications like Zyre and Malamute.
Yes, as a general rule, all STL containers in C++ are not thread safe. (E.g. Write operations need locks. Read ops also need locks if other threads can write to the container.)
However, I was looking at the specific loop example and only saw .begin(), .end(), and a pointer dereference which are all read operations. These should all be thread safe (assuming no other threads modifying the list) and I wasn't aware of any documentation that contradicts that.
>In other words: this code would be thread safe if protected by a readers-writers lock or similar synchronization mechanism
The outer context can affect whether or not an STL begin()/end() loop needs locks. For example, many programs can be coded to work in phases:
main() {
setup_lookup_tables(); // phase 1: fill up (write) to lists/vectors/maps
analyze(); // phase 2: spawn multiple threads to read the lists/vectors/maps
}
The code in the analyze() doesn't need to pay for millions of expensive locks+unlocks that slow things down. The programmer knows there are no writers to STL containers in the analyze() phase. With the assumption of no concurrent writers, the programmer can safely spawn a dozen threads with for(list.begin();list.end();) and get 100% determinism. Explicit sequential ordering can let one avoid unnecessary locks. In this limited sense, the loop is multithread safe for multiple readers.
However, even with the assumption of no writers to zcmq's "list", you still cannot spawn multiple reading threads of zlist_next() because the cursor itself is mutating inside the list. The result will not be deterministic.
That's the type of comparison I was making. You also have a valid point about general multi-threaded code covering all cases (implied concurrent writers).
Of course, it's not. If another thread removes the item that i refers to while i's thread is in its cout part, then this loop will go out in flames. Either functionally or semantically. And if it will not (meaning that List explicitly supports this sort of re-entrant case), then you basically get here not just a list, but some multi-purpose construct that can withstand a nuclear strike and the fallout - not something you'd actually expect when looking for a basic list container.
However this is exactly the kind of thing you'd typically get in C++. In contrast, with C you get a basic minimum that does what it says on a tin - links things into a list. You want it "multithread safe", you arrange for that externally. You happen to need that frequently, you wrap it into mt_safe_list container.
That's the key difference between C and C++ mentality. C++ does things universally and you can pick a sliver that you want to actually work with (leading to dialects), while C generally favors doing things in small functional units and combining them as needed. The Unix way. You can, technically, call the result of such combining a dialect, but that's quite a bit different dialects arising from the C++ usage.
That's also a good point. You can't use zlist* functions in recursive algorithms in single-threaded code.
Whether such recursive code would be an artificial contrivance or a real-world misuse is something left to the reader's imagination.
I notice that the zlist_copy() function was deprecated so you'd have to do something else if you wanted to pass a copy of a new "list" to subsequent recursive calls. I didn't dig deeper into the czmq documentation to find out what the recommended guidelines would be. (Although we'd guess that any copy operation would be O(n) and therefore thinking in terms of recursion would be an antipattern for czmq.)
It's zlist_dup() and you would not use recursion to process these types of lists. The API is designed for iteration. One case for recursion is when you use lists of lists of lists... you can then process such structures recursively. See zconfig for examples of that.
> So why is using something non-standard and non-universal like <czmq.h> not perceived as a "dialect" of C?
Somehow many fail to acknowledge that many write in compiler dialects, and think "my compiler == C language".
Yet commercial compiler vendors are always criticized for their language extensions or having other interpretations for the parts not defined in ANSI C.
Also, it's not always a problem of being able to work with other. Having a standard also means that other compilers will compile your C++ code.
We're not talking about a language, we're talking about a programming language, and standards and compilers are the only metrics which make that language "talked" by others.
Isn't a programming language both? Sure the goal is arguably to communicate some process for the computer to perform in a machine-readable way. But I can easily write two valid semantically equivalent programs which produce equivalent code when fed to a standards compliant compiler. However, one of these programs may be much more readable to a human because it follows an established set of coding practices. Perhaps a better word that standards would be "conventions" but either way, most programs also need to be read by humans at some point.
zlist_first and zlist_next can be made thread safe by using a thread-specific variable for the iteration cursor, rather than a simple member of the list structure.
Of course, that's a big, inefficient hack, and tied to a particular threading model (not generally reentrant).
Anyway, it's probably largely moot because neither example is safe if the list is treated as mutable (there is at least one writer concurrent with the reader).
So why is using something non-standard and non-universal like <czmq.h> not perceived as a "dialect" of C?[1] Any non-trivial source codebase beyond "printf("hello world")" will be a "dialect" of the programmer(s). When looking at the C source files of Linux kernel, Redis, and SQLite, etc, the syntax patterns, helper macros, string manipulations, etc do not look the same.
Also the author's example of,
is not the same semantics as: The C++ loop is multithread safe. The C version is not. For the zlist_next() to work, the "list" data structure needs to maintain mutable state in between subsequent calls. (Think of how something like strtok() works by mutating the string).[1]possibly because Pieter Hintjens is the programmer & CEO behind ZeroMQ and czmq.h. Therefore, it doesn't feel like a dialect to him?