69 comments

[ 8.3 ms ] story [ 174 ms ] thread
I'm sort of a C++/Stroustrup fan I guess, but this confused me:

> C isn’t simpler for C-style programming than C++ is [...]

Is Stroustrup really arguing that C is not simpler than C++? How can it not be simpler? C++ is essentially C with a ton of features added on top.

C is not simpler as C++ as you can program( with a few minor differences ) in C++ using C style. In that case both are equally complex.
His point is that actually you don't want to write plain C - you want to pick some C++ features that considerably reduce the complexity of your code.
Correct. Even if you simply changed all of your dynamic arrays to std::vectors, the code you write is now simpler, easier to reason about, and pretty much memory leak-free.
Your statement that, effectively, C++ can do everything C can, plus more, has the direct logical implication that C++ is absolutely more complex, not equally complex.

(FWIW, I don't fully agree with even that statement. C++ makes using e.g. enums with bitmasks much harder than C.)

He's just saying that if you write C in C++ it's as 'simple' as C itself. > C++ is essentially C with a ton of features added on top. You would just not use those 'ton of features on top'
Even unused code arguably adds to the overall complexity. But maybe that's what he meant, yeah.
Unused code adds to the complexity of a project you manage. However, you did not write the compiler: you are writing a program using the compiler. You don't have any unused code: you just have access to features you will choose not to use.
The complexity of feature set of a language does not translate to complexity of programming in the given language. That's because if the problem is complex, then it might be easier to learn the more complex language features than solving the problem with simple language features.
Those added features make for simpler code - I mean, almost anything that does string handling or requires dynamic arrays is way easier to do in C++.
Indeed. A simple language leads to complex code. And code complexity is the thing that needs to be avoided, not language complexity. You could write a Turing-complete language with a single instruction, for instance "subtract and branch if negative", and it would probably be the simplest language in the world, but writing anything nontrivial in it would be horribly complicated.
That whole paragraph is ridiculous. I'm commenting but really I should abstain because I feel I am lowering the tone with yet another great religious debate: C vs. C++.

"No. C isn’t simpler for C-style programming than C++ is, nor “closer to the hardware,” nor indeed more efficient. I have yet to see a program that can be written better in C than in C++. I don’t believe such a program could exist. By “better” I mean smaller, more efficient, or more maintainable."

Firstly, I'm sure what he's writing there is predicated on the C++ STL being available, the use of which will in turn increase compilation times, because there's a lot of templated code to compile.

I cannot see how you would implement strlen or malloc in C++ (EDIT: as efficiently and/or) more efficiently than C, particularly when you take casting void into account. It's not a far stretch then to imagine writing drivers and so on, where idiomatic C++ would be a distraction compared to C.

In the above his metrics, smaller, more efficient, more maintainable - I can't see any features that C++ has that will allow them to be proven true.

Any program that has to interact with a C library - Stroustroup would suggest library then to be wrapped to make it behave according to C++ style - if so - if the code is just calling a few library functions, C will be shorter and better by all his metrics.

The most convincing is maintainability, but C++ has many more ways to skin a cat than C, so where C had the overhead of understanding someone else's data structure, C++ pretty much does too.

[note: I use both C and C++ heavily, and I think you are interpreting Bjarne wrong here]

the use of which will in turn increase compilation times

sure, but that does not makes anything he says about it less true. You could even argue it has nothing to do with the discuession at all.

I cannot see how you would implement strlen or malloc in C++ more efficiently than C

He says "C is not more efficient" which means "C++ is as effecient, or more efficient". Which in turns means you are right, and he does not imply you are not (that's the "as efficient" part). Yeeha, you're both right, no need to start a war.

Stroustroup would suggest library then to be wrapped to make it behave according to C++ style

I'd rather think he'd suggest the library should have been written in C++ in the first place. Which is definitely shorter and better by his metrics.

The most convincing is maintainability

which again comes down to the "as efficient" part then.

Code that takes longer to compile is not better, more efficient, more maintainable. A C++ compiler will take longer than a C compiler to produce the same code from some C source code, providing it will compile, because they are not an exact match. I could argue about compilation times until I am very blue in the face, but I'll leave it at that and I won't do it here.

I am saying that I think that it will take more C++ than C to write malloc or strlen, because of casting to void, so I am saying he's wrong. I could be mistaken however.

I think a wrapper is less of stretch, to suggest that a library be rewritten in C++ as a defence of the simplicity of C++ would suggest a very large amount of cognitive dissonance.

"A C++ compiler will take longer than a C compiler to produce the same code from some C source code"

While this is technically true, I have in the past measured this, and the difference on my code when optimising was < 5%, so I think nothing to worry about.

Basically if you had to get a distro out the door, and 100 days to do it, given your example, if the code was written in C with a C compiler, you'd be done 5% quicker. It's not the sort of thing you can measure once and just forget about.

Then of course by moving to a C++ compiler, the temptation is to use features that take forever to compile!

Firstly, you can always assume the "C++ STL" is present, it is part of C++.

While implementing strlen faster in c++ is harder, implementing qsort faster is easier, as you have the option of compiling a version with the size of type, and comparator, known and inlinable.

Also, you could use a constexpr version of strlen on strings known at compiletime, and be sure the compiker will optimise out the call, unlike in C where you just have to hope the compiler will

Thanks for your reply. You can assume it's available in a general sense but you may not be allowed to use it, sometimes this happens.

Stroustrup made a bold claim that no program exists, I pointed out strlen and malloc - that there exist programs that can be written more efficiently by his metric - your e.g. qsort, I do not dispute, but I refuted his claim. I didn't say implementing strlen faster, I merely said implementing strlen is not by any of his metrics a superior experience in C++.

C would just use hardcoded strings or defines, with sizeof(). This is also a possibility in C++, but I was talking about the implementation of strlen not the usage.

Neither strlen nor malloc (nor qsort) count as programs under any pragmatic, non-nitpicking, definition.

Even the assumption that implementing, e.g., a memory allocation algorithm in C++ is not more comfortable than in C, does no lead to the conclusion that writing a device driver (your example) in C++ is not more comfortable than doing it in C.

I agree with you. Just to be clear: I'm not making bold statements like "I don’t believe such a program could exist.".

But you do admit they are programs! That was all I was trying to do, refute his ridiculous claim - albeit with something marginally less ridiculous. How about a program calling a C library?

The driver thing doesn't automatically lead to the conclusion - but it's not a far stretch to picture it being so. A simple thought exercise does not make a water tight argument.

I can imagine writing certain parts of drivers being better under his metrics in C++ than C but I don't see it in black and white terms like he does. I can see the opposite being the case also.

All that needs to happen for Stroustrup to be wrong is for one program to exist that beats his metrics in C instead of C++.

> But you do admit they are programs!

No, I don't – not in the sense that Stroustrup was using the term "program". strlen or malloc are not programs in the way that this term is commonly understood. His claim is not ridiculous, unless you're nitpicking by using definitions which are just not useful for the discussion at hand (i.e., is C++ better suited for developing non-trivial software).

As for the driver example: I guess we'll just have to agree to disagree, unless someone actually implements a device driver in C and in C++ ;-)

At least we can agree that a driver is non-trivial. How about a program that calls a C library?

The only project I know of that publishes in multiple languages, vtd-xml - the C++ library has 40k lines of code versus C which is 60k. I don't dispute that C can be more verbose - but it's not a stretch of the imagination to think there's a program somewhere out there that meets Stroustrup's requirements better in C than C++.

Not sure at what point we will have a certain agreement on what is trivial and what isn't. Here's an example of someone writing a lot of code to wrap a C library in to C++, and then doing a few calls to it.

http://www.codeproject.com/Articles/6343/CppSQLite-C-Wrapper...

I know what he means about teaching - I do a lot of systems (embedded) stuff, and love (well written) C++, but the course I did on it at Uni was awful. If that had been my only exposure to C++, I never would have wanted to touch it again!
For the sake of balance, here is Yossi Kreinin's list of things that are wrong with C++: http://yosefk.com/c++fqa/defective.html

PS. One of my biggest gripes with C++ is the lack of uniformity. Not only the language itself has insane learning curve, every project does so many trivial and not-so-trivial things differently that it's like learning another language each time you dive into a new codebase. From code formatting, documentation, and source tree layout to error handling, libraries and build system - for something so huge and complicated as C++ there's an amazing number of batteries not included. Even the things that are supposed to be standard in theory are often not so in practice. Damn, there's still custom string classes in the APIs of many popular libraries - almost two decades after std::string and STL!

PPS. And yes, there's a difference between having consistent conventions and trying to be perfect for everybody and everything.

or as bjarne points out in the article There can be no programming language that is perfect for everybody and for everything. C++ doesn’t try to be everything for everybody, but it is rather good at the tasks for which it was designed – mostly systems programming, software infrastructure, and resource-constrained applications and Don’t just say “I want a GUI library” or “there is no proper support for task-based concurrency” or whatever. We know that.
Yet, most operating systems have been written in pure C, resource constrained applications are still being written mostly in pure C or eventually C with classes (and mobile apps are not resource constrained apps anymore), and enterprise software infrastructure is written in a wide range of languages ranging from C, through Java to even Python. What C++ is mostly used for are games and desktop GUIs... :D
What C++ is mostly used for are games and desktop GUIs...

Hardly. Just about every interesting native application is written in C++. Chrome, Photoshop, Cubase, Ableton Live, AutoCad, Java, V8, any AAA video game, etc.

Whether you like C++ or not the fact remains that if you want to write an application of any complexity that really pushes the hardware it's just about your only reasonable choice. Maybe people still write kernels in C but kernels really aren't that complex compared to the applications they run.

You listed mostly desktop apps and games = exactly what I said. And even in this area, C++ is not used exclusively - see Minecraft (Java), Azureus (Java), Gimp (C), VisualStudio (new code mostly C#). So no, not every.

BTW: There are many interesting and successful complex applications really pushing the hardware that were not written in C++: Cassandra (Java), Hadoop (Java), PostgreSQL (C), Netty (Java), Nginx (C), TeX/LaTeX (uh, this one is really interesting). So, again, C++ is not the only reasonable choice when performance matters.

(comment deleted)
Scaling across multiple machines is a completely different problem than pushing a single machine to the limit, with completely different constraints. Once you move beyond a single CPU and you can tolerate higher latency then languages like Java start to become more viable.

When you need maximum performance and minimal latency from a single machine C++ is still the first choice and the number of high-performance apps written in C++ vastly outnumbers those written in C. Check high frequency trading for another example.

There are companies doing HFT with reatlime Java and custom hardware. Sure, C++ has its place there, too, but again: not the only viable option.
The fact that this list has been around for a while doesn't make any less inaccurate and massively biased. That's basically a milder version of that "Indian Programmer Competency Matrix" - both are notable for how frequently they are plugged into conversations and not for their actual content.

[0] http://sijinjoseph.com/programmer-competency-matrix/

Stroustrup says:

  The real novelty here is the return statement: Note that
  I return a potentially huge vector by value. In C++98,
  that would typically cause the copy of all elements of
  res, potentially many thousands of elements. That would
  be a serious performance bug. In C++11, vector has a
  “move constructor,” so that rather than copying elements,
  the representation of res (merely three pointers) is
  “stolen” for use in the caller and an empty vector is left
  behind. After all, we are just about to return from 
  find_all() and won’t be able to use res again. Thus,
  returning a vector by value costs at most six word
  assignments independently of the number of elements.

  Move constructors is a simple facility available to every
  programmer and used by all standard-library containers
  implemented as handles. This allows us to return large
  objects from a function without messing around with
  memory management (explicitly) using pointers and
  free store.  
Are most C++ programmers excited by this? Is the idea that people should start writing code depending on this behind the scenes behaviour, or that we now have a way to speed up poorly written code? It feels like an awful lot of effort to avoid returning a pointer. And if I were actually worried about the performance, I wouldn't feel comfortable just hoping it happened. Is there any confirmation by the compiler that it handled this in the way the programmer wanted?
If you're living without a garbage collector, avoiding that pointer return is potentially quite a big deal.

"Just use a GC" is a viable answer to this in many circumstances, but probably not all.

"Just use a GC" is not a viable answer in the C++ world, because for C++, contrary to Java, there are no GCs of acceptable throughput and pause times.
You can use automatic reference counting.
It is sometimes handy, but generally compared to GC it is slow and incurs lots of contention in multithreaded programs.

Imagine you return a reference-counted pointer from a function. Then every call to this function is a lock/unlock and CPU cache synchronization. And if the pointed object is shared between threads (even for read-only access) this might lead to lot of contended locks/unlocks, which is extremely costly.

The synchronization you mention is really rare, it only happens when you make a copy (not a move!) of a std::shared_ptr.

If you have a std::shared_ptr<X>, then most of the time you will be passing around "X &" or "std::shared_ptr<X> &" and no copies will happen.

Do you have an example of why a function would be returning a std::shared_ptr<X> by value that wasn't a move?

To return a shared_ptr you first had to create it. So even if it optimizes the move, it doesn't optimize away the creation of the pointer and its destruction.

As of using naked references, you're right, but if you can use naked references, why ever use shared_ptr? Naked references are fine when you know the lifetime of the object.

Don't get me wrong - shared pointers are useful in many scenarios, but they are not a general replacement for GC.

BTW: Here is the post of someone who thought he can use shared_ptr as a poor-man's GC: http://stackoverflow.com/questions/2502394/the-cost-of-passi...

shared_ptr took 17% of time. This is far beyond what GC takes even in a very memory heavy Java application (typically GC stays below 1% overhead).

I'm not saying you shouldn't use shared_ptr at all, but only: be careful.

Novices also sometimes erroneously pass std::vector by value, that doesn't mean std::vector is slow. Maybe you did not read the entire post before linking it. The conclusion was he changed his code to pass by reference and 17.77% became .27%.
Thanks for pointing that out, I had heard horror stories before and was willing to take 17.77% at face value!
You can use naked references because most functions aren't acquiring ownership of a shared_ptr.
I always hear about how use of mutexes is costly but i never actually encountered a significant performance hit from using synchronization. Unless, of course, you have like 10 threads frequently accessing the same memory, which is more a design issue.
Uncontended interlocked operation is typically about 100-300 CPU cycles. Uncontended mutex is about 30x-50x that. If there is contention, multiply everything by another 100x. Additionally these instructions impact not only the calling thread but also other threads by forcing cache synchronization.

Therefore a relatively simple operation like pointer assignment can be one cycle when using GC and a hundred to several thousands cycles when using refcounting. Sometimes you can ignore this overhead, sometimes not - depends on how often you do that.

http://software.intel.com/en-us/articles/choosing-between-sy...

On what CPU? on my i7s it's much faster. on the order of 50 cycles for a uncontended mutex lock and release i think. I can dig out the values if anyone cares.
https://en.wikipedia.org/wiki/Boehm_garbage_collector They use this in Mono, what do you mean by throughput and pause times? Is it far inferior to the Java GC?
They stopped using Boehm since 3.2 release of Mono. In certain cases the new GC is 10x faster.
It is. Actually it is in a completely different league: 1. non-generational (= low throughput) 2. imprecise (= no guarantees on freeing unreferenced memory) 3. stop-the-world (= long pauses) 4. non-compacting (= allocation is slow and fragmentation still might be a problem)
The compiler is required to put calls to nove constructors in specific placez, it doesnt change based on compiler or optimisation level.

The problrm with returning a pointer is now someone has to worry about when the memory that pointer refers to gets ckeaned up. In my current 50,000 line c++ project, there are exactly 3 places where I call new or malloc, and then have to manually worry about cleaning up the result.

You don't just hope that it happens. Move semantics increase the usability of value-type semantics in C++ - you can now do more things with "values" without shooting yourself in the foot (performance-wise) i.e. you can replace some previous uses of references, pointers etc. (note however that moves, while potentially cheap, are not free. There is still RVO.)

It behove users to learn about move semantics before using them (or rather before assuming that they are implicitly using them.) The topic is unfortunately complicated.

I am not that good at C++ so it would be really nice if somebody can enlighten me:

Isn't this already the case with some (all?) compilers in pre-C++11?

http://www.parashift.com/c++-faq/return-by-value-optimizatio...

NVRO works by constructing the object once... (EDIT: detrino points out this is wrong, the object is not constructed first before passing, it is only a memory address that is passed, it's the return value that is constructed on to this address)

It is passed as a hidden argument to the function, assigning the return value to that argument passed in. Prior to the NVRO, the object was constructed once, a temporary was constructed and then copy constructed to give the result. You can test out the NVRO yourself by putting a printf in an object's constructor, and have the object returned from a function on to an object of the same type.

Stan Lipmann's Inside the C++ Object Model explained it best for me. NVRO applies to the vector object.

The difference here is that Stroustrup is talking about the elements of the vector, not the vector itself, and how the move semantics remove the need for constructing/copy constructing them.

What you are talking about is really just the calling convention common on many machines. When you have a function returning a struct, what really happens is that the return address for the struct is passed as an extra argument. No construction of the struct happens prior to this, its just an address. Compilers are able to take advantage of this calling convention in many cases to elide copies, and the result is RVO/NRVO.
Are you alluding to some sort of hardware mechanism by mentioning machines? In what context are calling conventions machine dependent?

The reason it's notable in C++ is because the code that would normally be called in the constructor is not called as many times as the person writing the code might expect.

In C++ structs can have constructors, but usually have an empty default constructor supplied by the compiler, this is more of an issue when structs/classes have user supplied constructors.

Here's an article giving more specifics of the history of NVRO from Stan Lipmann. Interestingly, NVRO was not added to Visual C++ til 2003, and Lipmann prefers NVRO off by default. I think I recall an NVRO flag in that compiler. NVRO was available in cfront and Zortech compilers in the early nineties. [1]

[1] http://blogs.msdn.com/b/slippman/archive/2004/02/03/66739.as...

The calling convention your compiler is using is dependent on a number of things [1]. It is this calling convention that dictates how values are returned from a function, not whether or not RVO/NRVO is happening. Also, what is being passed as an extra argument, according to many calling conventions, it not really an already constructed object, but an address for an object to be constructed into. Under certain situations the compiler is able to elide copes, which as you pointed out, leads to your copy constructor to never be called at all, even if it has visible side effects. I think these are important distinctions and hoped pointing them out would be beneficial.

[1] http://en.wikipedia.org/wiki/X86_calling_conventions

Yes, thanks for pointing the calling conventions out, I didn't really know what you were getting at.

The hardware calling convention does dictate certainly what code the compiler can put out.

If the compiler does not support NVRO and emits code that causes multiple copy/constructions it doesn't matter if the hardware supports more efficient behaviour.

I edited my earlier comment with your correction, thanks.

The compiler is not required to perform RVO/NRVO and when it can be done depends on some surprising things such as the calling convention of the machine you are targeting. It's just an optimization the standard allows compilers to do (eliding copies).

What C++11 provides is a new kind of type that says "it is safe to steal the guts of this value". You can use this to overload a copy constructor or assignment operator to do "moves" instead of "copies". Because it is baked into the type system, it is guaranteed to happen when you expect it. While eliding a copy essentially has 0 cost, and the compiler will still prefer this over a move, moving a vector can have a very small cost (copying a few pointers), but it is still O(1) instead of O(n).

I don't feel it's behind the scene at all, the behaviour is fairly straightforward and easy to understand. It seems pretty error-proof as far as I'm concerned.

We already trust the compiler to do a lot more tricky optimisations (loop unrolling, inlining, dead branch elimination...) so it doesn't really trouble me.

Loop unrolling, inlining, dead code elimination does not change the asymptotical complexity of the program. This optimisation does.
It's not an optimization: it's move semantics. And it's default behaviour for any standards-conforming compiler. Moves can be optimized away still, but by default, returning a std::vector from a function is a constant-time operation.
Constant folding can (theoretically) turn a O(N) loop into constant-time, and may trigger further dead code elimination. Your typical template metaprogram for computing Fibonacci numbers will do the same.

The time when mortals could more or less 'see' what the memory usage and execution time of a C++ code fragment are is well beyond us.

The alternative to returning a vector by value would more likely be passing an empty vector by reference and then adding elements to it, then returning void. Returning a pointer instead (or unique_ptr) would be replacing value semantics with reference semantics which would be less efficient and inconsistent in interface. That's what move semantics bring to C++, consistent interfaces and efficiency.
I've basically freshly come to C++ in a work environment, and C++11 is all I've learned so far. Range-for loops and smart pointers and move semantics are wonderful. I still haven't adopted to a perfect idiom for calling functions, though: It's straightforward if I call a function and it returns a single object with move semantics. But what if I want to mutate multiple things with a function call (say, go through a vector and throw out indexes below a certain threshold, updating an unordered_map so as to specify the names and locations of certain columns). Do I return one, while changing the other by reference? Do I return a pair or a struct that only exists for me to quickly unpack? Do I make the function void and change both by reference?
There are languages specially groomed to replace C++, like D and Rust, but they might never; Not in this decade.

The day LLVM gets rewritten in Rust, that day Rust barely starts replacing C++.....