Hacker News new | comments | show | ask | jobs | submit login
Ways to Avoid Complexity in Modern C++ [pdf] (vitiy.info)
95 points by ingve on Dec 7, 2015 | hide | past | web | favorite | 53 comments

Hm, this seems to be more advice on how to use C++14 as if it were its own language, or as if it were Java, or as if it were Scheme.

One problem I have with the advice to use the newest language features in C++ is that it sacrifices one of the biggest advantages to writing code in C++: the ability to compile for multiple platforms. It's extremely unlikely that one has a compiler for every platform that supports all of these features. Additionally, each of these language features adds a small bit of convenience to the developer, while making the compiler substantially more complicated (and hence, more prone to errors).

To summarize the points made in the first 10 slides, which is far as I read (sorry)-

1) Always use auto.

2) Use for-each.

3) Don't use new or delete.

4) Use STL for everything.

5) Use smart pointers.

> biggest advantages to writing code in C++: the ability to compile for multiple platforms. It's extremely unlikely that one has a compiler for every platform that supports all of these features.

Do you have war wounds from the C++ ARM or K&R C days?

I have. That was one of the reasons many of us enjoyed using Java.

It was much easier than dealing with all compiler and OS compatibility issues.

The situation with C++14 compatibility is way better than on those days.

I agree strongly with these comments.

I used template heavy C++ back in the late 1990s, rolling container classes implementing smart pointers it was tricky to get good code compatibility across a range of compilers: Edison Design Group, icc, MSVC++, gcc. Ultimately I found myself reassessing the tradeoffs in using C++ for many projects.

Fast forward to 2015 and the situation is much better - even for "soft" embedded scenarios. GCC and clang have good reach and MSVC++ is evolving with the language.

Engineering decisions are often about making tradeoffs and with the state of C++14 and things like C++ Core Guidelines and good support for RVO/move optimisations I find the appeal of C++ has broadened again. And there is good support for modern C++ on many platforms.

Of course there are platforms out there where C++ is still not suitable. For example I'm currently doing a lot of 8086 real mode work, where due to external constraints all the code has to be written in assembler. Neither C or C++ are candidates for me in this work. These platforms will always exist. But many embedded platforms with smart phone level of processing power are solid targets for modern C++.

Of the listed features he suggests. All of them are C++11 features that are supported in Clang,GCC and VS. Which gets you the 3 major platforms for computing,mobile and cloud computing and through ARM the vast majority of cost effective embedded hardware.

> it sacrifices one of the biggest advantages to writing code in C++: the ability to compile for multiple platforms.

Is that really one of the biggest advantages to C++? Both of the other languages that you mention, can target a huge amount of platforms. Java, Scheme. In fact, I think that's a feature of very many modern languages, And specifically of C, which may be a much better choice if compatibility is your preference. It seems that the choice to use C++ is not likely motivated primary by platform support for most developers.

For some time I think of C++ not as a programming language, but as a make-your-own-programming-language-kit. It's because practically there is no the C++. Everyone uses different subset or even coding style. C++ ABI is not as interoperable as C ABI (if you can even say that it exists). Almost every bigger library reimplements Object - the base interface of everything. Then they need wrappers and adapters between those different object models. Qt for example is not written in C++ - it has to be compiled with their MOC to add signals/slots and introspection around other things. But all this ad-hoc languages suck in regard of tools. Their error messages are unintelligible and everything relies on convensions (hopefully enforced at least by code reviews). I could paste here quote about ad-hoc reimplementations of Lisp...

C++ is still necessary (evil) in some cases, but as a C++ programmer I have enough and I plan to move to other languages.

Yup, I've had the same experience. Every C++ project or framework pretty much has its own style and I don't just mean code structure.

The advise is pretty useless. Complexity often comes from incorrect abstractions, not from language features.

Programmers can build very complicated systems using the simplest languages available.

A language, and especially a low level language, often jumps in front of your nice abstractions and obstructs them with all the unworthy low level details and its exposed bare guts.

Techniques for eliminating this leaky abstraction are important indeed.

I agree, but I think "incorrect" is the wrong term, "suboptimal for the problem" would be a better way to phrase it.

A little confused about the authors decision to do operator overloading with the pipe operator rather than a function chaining interface for the functional bit. Wouldn't a function chaining interface be simpler and use less magic?

You're referring to something like?

I believe the issue is that it cannot easily be extended with new functionality since all of the methods must be members, whereas operator| can be defined as a free function which allows one to write new chainable functions without altering the base library.

I agree that a function chaining syntax would be preferable. LINQ to Objects in .NET can do something like this because the language(s) support extension methods.

Yes, this was what I thought too. I understand where this "piping" of functions comes from and that it deemed to be a readability improvement (for *nix users at least), but it is unnecessary because like you've mentioned there already is a way of doing that in C++ and because at least for me the notation is a little bit confusing (as I'm inclined to read it as bitwise "OR" on function's addresses) and requires me to rewire my brain in a counter-C++ way!

I'm guessing the perceived burden of debugging when dealing with function chaining (especially when you consider side effects from inheritance, it can get ugly). This is actually a case where operator overloading could allow for more maintainable code. Usually it's sort of a selfish design choice which provides marginal developer happiness and considerable ambiguity and bloat (in my opinion).

Did you miss the part that said "[SYNTAX IS OPTIONAL !] Exact operator choice is up to You" ?

Why criticize the author about a point they went out of their way to tell you was just a stand in for whatever syntax thought made a better choice?

I think it approximates the pipe("|>") operator that you see in quite a few functional languages.

And it is a poor choice to consider that in C++, because in here there already is a default non-compatible interpretation for their expression.

It depends on the context. The pipe in a data flow sense will be working on data structures, a bitwise or will need to work on two plain data types.

C interop. The cause of, and solution to, all of C++'s problems.


Presumably, C interop is the cause of C++'s problems because it was the impetus for carrying forward all of C's warts into C++. And it is the solution because it allows you to put C++ code behind a C-compatible interface so that you can use it from a language that isn't C++.

The "Design and Evolution of C++" is a very nice book from Bjarne explaining his compromises regarding what eventually became C++98.

Don't use it?

Nothing can salvage C++ at this point. It needs to be EOLed and put to rest.

When you give me a language that I can specify explicit memory layout and support for generics we can talk.

Otherwise C++ is going to continue filling the space for large, performance sensitive applications.

No flame-bait here, but if you are seriously looking for that, how about Ada?

Realistically, I don't see C++ going away for a looong time (if ever), simply because of the huge amounts of code written in it. But if one were looking for an alternative that matches C++ both in performance and control over memory layout, Ada is where I would start. It has generics, you can do OOP, it avoids many of the syntactic pitfalls of C++... As a bonus, the language standard and the rationale are available for free.

I can think of a few reasons to avoid Ada. For one, resources and community for this language are basically nonexistent compared to C++. The history of the language is obscured in proprietary implementations which has led it down the same road as Pascal in terms of longevity. Ada has "bad parts" in the same way that C++ does, although this is mitigated by SPARK.

Concurrency / tasking in Ada is simple and beautiful, however. One of the most compelling features of the language, especially coming from C++.

I have observed a strong trend in avionics software development to move away from Ada towards C++. I have not yet heard a concrete reason why. I might guess that it's easier to hire people with some existing C++ experience than with Ada experience, but understanding the programming language is pretty inconsequential compared to understanding the product domain.

How much does Ada rely on garbage collection?

Doesn't have one. Reference counting is available as a (what looks like third-party) library: http://www.adacore.com/adaanswers/gems/gem-97-reference-coun...

I guess you could say that is even less reliance on GC than C++ has, with smart pointers being part of the standard.

Common Lisp's foreign function interface (either implementation specific or cross-platform a la CFFI) allows for the explicit definition of how a foreign structure should be laid out in memory.

Even if I'm not using Lisp for the end product, whose SLA may not permit a runtime with garbage collection, I always find it invaluable to define low-level structures with a Lisp whose machine code compiler is available at runtime for experimental interactive development.

A free and open source Lisp that you might enjoy experimenting with is SBCL, available from http://www.sbcl.org.

Load up the REPL, and follow the manual available here http://sbcl.org/manual/index.html#Foreign-Function-Interface.

SBCL also allows you to interactively augment the machine code compiler, install new intrinsics, and disassemble any function interactively.

Even if you never use it for production code directly, it might become an invaluable tool in your low level development process.

Does Rust fit into those specifications? I understand library support is probably not quite there yet, but I was under the impression it could fill the role of C/C++.

They get close but I think there's a few restrictions(rightly so from a safety perspective) that keep untyped arena allocators and in-place loading from working(see FlatBuffers for a fantastic library for in-place loading).

Edit(since I can't reply yet to child):

By in-place loading I don't mean placement new(which untyped arena covers). Usually in-place loading means constructing an object(usually just by reinterpret_cast<T*>(data)) over a block of memory read in from disk/network such that all data members "line up" with data as it was serialized.

This also includes the ability to mutate the in-place data but have initialized to some sane values. 99% of the time this is paired with either some sort of preprocessor(usually written in the same compiler as the reader to guarantee data matching).

Quite a few games use this technique to allocate levels/fixed entities such that once you've pulled them disk you just need to do some trivial pointer fixup and away you go. There's some pretty incredible speed increases(100-200x) but obviously it's incredibly fragile and complex to get right.

Flatbuffers does a good job of trading off fragility for usability by encoding offsets in the placement data. Reading data is slightly slower due to needing to calculate the offset into the data but on the plus side it doesn't depend on compiler specific layouts :).

By "in-place loading" do you mean placement new?

And while Rust does have restrictions for safety, unsafe should give you the power to do anything equivalent C or C++ can do. Of course, many idioms are different, especially for safe Rust.

  > Edit(since I can't reply yet to child):
HN #protip: if you click on comment link itself, it will let you comment earlier.

Ah, I see. This isn't my area of specialty, but it _sounds_ like what Cap'n Proto does? Anyway, it doesn't sound impossible, though it might not be the most ergonomic. I'll check out that library, thanks.

Learn something new every day!

Yeah, Cap'n Proto is very similar, FlatBuffers has the advantage in that it compiles against almost all versions of Visual Studio where Cap'n Proto unfortunately decided to use some C++14 features.

FWIW I think I've seen some ports of FlatBuffers into Rust since it's much easier to handle due to the offsets and translation on return value. The former(matching compiler layout) was the part that I was mentioning that seems like something a bit gnarlier than rust would like.

Gotcha. Yeah, so the Rust Capn' Proto does compile-time codegen to write out all the structures, so the grossness is hidden away, or at least, you don't have to deal with writing it out. I believe it's basically the same as the C++ version.

Yes, support for generics. _Full_ support for generics. And RAII!

The D programming language anyone?

If D manages to get garbage collection out of their standard library and there is a one click IDE with step through debugging and code completion, then D could be a major contender. Right now it has a few big barriers that prevent it from taking significant market share on C++.

D has full generics, explicit memory layout, and RAII.

From what I've seen of D(I haven't used it in detail) there isn't enough materially different(like say Rust) to warrant the loss of libraries and IDE integrations available to C++.

Amongst the parts of Phobos having an equivalent in the C++ standard library (algorithm, string, etc.), which ones still require garbage collection?

Only one of the algorithms in Phobos still uses the GC.

Also, library support.

Free Pascal?

Have an up vote for a completely fair point. They keep adding stuff to C++ when the language is already ungodly complex and ambiguous enough.

Just stick with C if you need low level control.

I don't completely disagree with you, but in practice it's really hard to go back to C sometimes - there are a lot of tasks that are painful and error-prone to write in C. Complicated initialization and teardown logic that can fail mid-way is much easier to write incorrectly without RAII and scoped initialization objects, for example.

But see, I only need low-level control for parts of a project. For the rest of it, it's really nice to have the STL, and the move operators, polymorphism, and and and...

In favor of what? There are lots of things it would be crazy to write in c++ in 2015 ... on the other hand there are some things that really don't have compelling alternatives.

I feel like Rust is getting there but AFAIK there isn't support for an untyped arena allocator(just homogeneous arena allocator) which you need if you don't want to be playing around with raw byte arrays to do Data Oriented Design.

I think that they'll get there but it's going to take a bit.

Also, I'm not aware of anything that has a wider set of compile targets(all the way to some larger uC up to web via emscripten).

I used to program C++ a lot, several years ago.

My way, today, to avoid complexity in C++: Avoid C++!

C++ is at least three languages and the complexity is at least that of three languages. If you really want to be faster at programming and don't need to have the last 5% of performance, do you a favor and use an other language.

For some of us, that 5% is incredibly valuable. Really, any HPC application is going to either be written in C/C++ or Fortran.

It goes without saying that modern fortran is a hideous language (even though it's still better than Fortran 77). C is nice and simple but without things like namespaces and classes you're going to end up with huge function names. Not to mention a high risk of memory leaks.

C++ was awful years ago. Honestly C++11 allows you to write fairly nice code. By no means a guarantee you'll write clean-ish code, but it definitely gives you the tools to do so. Plus modern C++ compilers produce really fast code (that leverages SIMD/vectorization, etc.).

I'm a huge fan of Go and Rust but the former is often dogmatically minimalist to the point of not including things like templates, operator overloading, etc. Not to mention garbage collection/runtime which is a big no-no for numerical computing use. The latter is still not mature enough to fully commit to (and is nowhere near the speed of optimized C/C++).

  > (and is nowhere near the speed of optimized C/C++).
Please file bugs! We consider them as such.

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact