Skip to content

Instantly share code, notes, and snippets.

@9034725985
Forked from alexpana/hacks.md
Last active November 21, 2021 09:19
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save 9034725985/f7e7841fb05435f9698e to your computer and use it in GitHub Desktop.
Save 9034725985/f7e7841fb05435f9698e to your computer and use it in GitHub Desktop.
C++ is a hack

Various 'features' of C++ that show the hacky / inconsistent way in which the language was constructed. This is a work in progress, and currently contains some of the reasons I can remember why I've given up on C++. If you want to contribute, leave your favourite "hack" in the comments.

  1. (in)Visibility: C++ allows changing the access modifier of a virtual function in the derived class. Not only does C++ have no notion of interfaces, it actually allows subclasses to hide public methods from the superclass.

  2. Operator over-overloading: One of the increment operators takes a dummy int parameter in order to allow overloading. Can you tell which without googling? (hint: it is postfix).

  3. Exception unspecifiers: C++ has two types of exception specifiers: throw() and nothrow. The first is deprecated (because 'we screwed up, sorry, let's forget about this terrible mess'). The second one guarantees its contract by terminating the application when violated. That's because functions declared nothrow can still call legacy functions that don't declare anything (backwards compatibility and such), so the compiler can't validate your claim, choosing to explode in your face at runtime.

  4. Dependency hell: Without adding another layer of indirection via the PIMPL idiom, changes to the internal structure of the class propagate to all its users, making compilation painfully / unnecessarily slow. Granted, the C++ object model requires this when the modified class is used as a value field inside another class, but C++ has no way of distinguishing between use cases. This is what you get for using a text preprocessor for handling dependencies.

  5. Binary incompatibility: Linkers are not standardised, so binary compatibility must exist between compilation units when linking. Not only are compilers incompatible, the same compiler used with different flags can generate incompatible obj files. Write once, compile everywhere.

  6. Multithreading: C++ did not have any standard multithreading implementation until C++ 11.

  7. RValue References: C++ has value semantics, which means it generates a copy constructor to deep copy value fields (convenient huh?). While the standard committe thought it was pretty cool, they eventually realised how many times objects were unecessary or accidentally copied behind the scenes (return values from functions, array operations, etc). So they invented 'move semantics', 'move constructors', and a new synthax for overloading on ephemeral 'rvalue references': &&.

  8. Name mangling: The C++ compilator was originally a preprocessor for C (CFront), and still maintains binary compatibility with C. C doesn't allow function overloading and doesn't have namespaces, so any function can be uniquely identified through its name (to avoid collisions, most libraries use a prefix when naming functions, such as gl*). Since C++ has function overloading, namespaces and classes, it allows multiple functions / methods to share the same name. The C++ compiler solves this problem by generating a unique identifier for each function. This is called name mangling, and uses the function's name, parameters, return type, namespace and class to create a unique name. The result is a horrible abomination that makes linker errors a pain to descipher. It is also non-standard, so every compiler has its own name mangling scheme, making them incompatibile with each other. - https://isocpp.org/wiki/faq/mixing-c-and-cpp

  9. Inheritances: Since there are multiple access modifiers (public, protected, private), why not multiple inheritance(s)? There's even a virtual inheritance! While their meaning is beyond the scope of this rant, suffice it to say there are aspects that make them non trivial to understand / use. Scott Meyers wrote in his famous Effective C++ book: "protected inheritance is something whose meaning eludes me to this day". And he's a C++ guru.

@RockBrentwood
Copy link

Rants are useless without an endgame. The endgame here is: if you don't like the language, then make something better that does not have the problems you cited.

On the plus side: cfront, itself, can now be simplified substantially by virtue of C's upward migration. Take it (like Release 3), re-target it to C2011, then re-source it to whatever alternative you want to design, and test things out that way. After you find something you like, then concatenate the (Your Language) -> C trans-compiler to a C -> Backend processor (e.g. cparser/libfirm) into a (your-language)parser and you have a compiler and you're all set to go.

At the bare minimum, you should also have a C++ -> Your-Language trans-compiler so that you can carry over all the libraries that are now out there over into the new language. Your language should be rich enough to allow efficient conversion without the need to shoehorn anything. Shoe-horning, whenever it occurs, is a clear sign that something is missing in the target language ... which can be used as a way to guide the target language's design.

Support for multi-threading, by the way, was originally slated for the earliest versions of C++. It was for this reason that Release E and Release 1 of the cfront trans-compiler had a "task" subdirectory in the "lib" directory. But it proved to be too much for the time, since the priority was to get some language out there that was both workable in the near-term (not something that would have to wait for years for better ideas, technologies, etc.) and a language that would not be a radical departure from C for programmers at the time. So, it was deferred to a later time. That later time was 2011; the extensions, themselves, may be viewed as an completion of the original design of C++.

In the 1980's, nobody really had support for multi-threading (particularly: thread-local variables!) The closest you got to multi-threaded support in language that was the experimentation Fortran did with multi-threading in the 1960's onward and a few experimental / academic multi-threading languages that were around at the time. Remember: POSIX 1a - originally POSIX 4 - didn't come along until the early to mid 1990's. In fact, I was sent a copy of a POSIX 4 reference at the time to review. There was no ISO standard for multi-threaded programming before this.

That's why I used assembly for embedded control projects in the 1990's. I could easily craft a multi-threaded run-time executive in less than 1 page of code and put together an assembler whose syntax looked enough like C to make future transition to C easy (which was actually put to the test at a later time and proved out). In contrast, most people at the time - whether they programmed in assembly or C - wove together all the threads by hand into a giant scan-and-run loop that made everything un-maintainable. Some processors were ideally suited for rapid thread-switching, notably the 8051 family which has 4 easily-switchable register windows to help in supporting thread-safe variables, but few people (other than myself) ever fully used these capabilities, since few were skilled in multi-threaded programming.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment