Criticism of C++

Started by
143 comments, last by Ryan_001 8 years, 3 months ago

C++ is indeed flawed. I think it would actually be quite easy to replace. I'm serious. You know why C++ hasn't been replaced yet? It's because every language that begins life as a competitor to C++ is seduced by some outside agenda that suddenly pulls it out of the running. The truth is no one wants to make a hardcore systems language. Halfway through making their "C++ killer language", they switch to an applications language, add garbage collection, remove pointers, mandate "safety", mandate exceptions, etc. For example, I can easily admit that C# is a better language than C++: it's more readable, easier to organize, more fun to use, etc. But at the end of the day, it's a garbage collected language that ties my hands with regard to low level constructs (or forces me to use tons of attributes and/or unsafe code to accomplish simple things in C++).

The way I see it, there's little point in criticizing C++ at this level until someone wants to step up and actually strive to build a replacement. I hope to do that one day. I'm actually working on a lexer/parser of my own. I'm sure I'll do a terrible job as my background is not in programming languages, but I want to set the ball in motion. So, what beef do I have with C++? Lots of things. I just wanna go down the list and fix them.

  • Replace text-oriented #include/#define with proper module/macro system and a proper namespace/package system
  • Bring naming conventions out of 1970
  • Have nicely standardized int16/int32/int64 (instead of typedefs tucked away in the library)
  • Hopefully introduce a handy build system (new kind of make file)
  • Actually allow zero-cost abstractions (force inlining, etc.)
  • Add nice portable libraries for things like SIMD and networking
  • Expand enums to full fledged data tables
  • etc etc etc

With all that said, are there actual C++ competitors out there? I don't really count Rust. The syntax is ugly as sin, and the obsession with safety is unappealing.

Amateurs practice until they do it right.Professionals practice until they never do it wrong.
Advertisement

If that is something that makes the language unviable for you, use a different one.


Can we not have a civil discourse on the pros/cons of a language without resorting to this?

He actually explained in that same post why. As he explained, even in 2015 he's writing code where char is 32 bits.
And suggesting to use a different language is not uncivilized.
It's simply a brutal truth. Some people want C++ to do stuff it's not intended to do, solve issues that is not supposed to do; and changing C++ to please this people will anger those who need C++ the way it is now.
You can't satisfy everyone at the same time. Those unsatisfied can move to another language; because what they want from C++ is not what they need.

The evil's of premature optimization are always taught in software design, get it working correctly then worry about shaving off a few bytes or cycles here or there.

Yet as L. Spiro points out, a lot of people get it wrong.

What we have is the opposite, where the default is fast and then we have to over-ride it with the 'correct' typedefs.

That is simply not true.
I still don't see what this "discussion" is supposed to be I guess. So far it's just people saying "I don't like X", which is fine, you don't have to like X. If you don't like X you have the following options:

1) Propose to the people that maintain language A that they should change it, how they should change it, what benefits the change provides, and how you're going to fix (or not break) the code that relies on the old behavior of X.
2) Switch languages.
3) Write something in language A that either circumvents or wraps X to something you like.

I'm not trying to attack anyone or shut down discussion, simply trying to point out that feature X that you don't like has very valid reasons for existing and, because of how prolific language A is, cannot be lightly changed.

(Intentionally not mentioning any features or languages because it's pretty much true of ANY language and feature you want to pick, as long as the language is reasonably well-established and the language maintainers care about supporting their users)

Maybe point 1 above is where discussion can happen, but I haven't seen that in this thread yet because there's a focus on "I don't like it" instead of "here's how I would do it differently and here's why my idea is better". Or, in some cases, the complaints are already addressed in modern version of the languages, or coming soon anyway.

With all that said, are there actual C++ competitors out there? I don't really count Rust. The syntax is ugly as sin, and the obsession with safety is unappealing.

Well, I'm sure there are. Most of modern languages have been created to give a counterweight to old C++ and become better than the old grandfather. It's interesting however that majority of new languages are written in... C/C++. For example Java, Mono, Lua etc. Hence, even when looking for an alternative to C++ it's really hard to run away from it. It's no different for ObjC either. ObjC/C++ is built on top of C/C++ (after ak the suffix "C" and "C++" stands there for a reason).

To be more "on topic" I must say there are lot of things I don't like in C++. Heck, I got "stuck" with old C-style ways of printing strings etc. because I just don't like overloaded >> and << operators (even in Java I use String.format() which is very similar way of formatting strings as printf() in C, because I just can't stand building strings with '+' operator). It may also be annoying to organize headers etc. but with experience it stops being the problem. I like and dislike templates. Templates are very powerful tool but also wrong use of them may lead to unreadable (and hardly debuggable - try to debug boost) code and bloated executables. But all of it is just nit-picking.

The core of C++ is C - small, fast and reliable subset of the language that can be always used as a fallback. As long as C capabilities (including powerful "asm" equivalent keywords) are alive, C++ will be too. There's a great example in modern game development. For couple of years there's been a talk about data-oriented-design techniques. Engine programmers talk more about ECS, DoD, layouting and allocating the memory, caches, better threading and - in general - coding "closer to metal". We walked full circle from the time when video games were written in pure assembler of particular CPU - close to the target hardware. Now we're getting there again! C++ and object oriented programming became a bottleneck for performance. So what do we do? We build the code around data, using more "aggregation-over-inheritance" pattern, trying to know EVERYTHING about data and memory we use (in other words - give personal touch to each used pointer ;) ). All of it we can do in pure C/C++! Now, look at the modern languages. They divert from this path. They force OOP which stands against what we've learned over last few years about utilizing memory, CPU caches, GPU, game data. If we look for the alternative, we should take in account all that C++ stands for. Otherwise each new language is built to stand next to C/C++, not instead.

Less "implementation defined" behavior, either it's ok or it's not.

Funnily, I was going to say the exact opposite. biggrin.png

Sometimes, declaring something undefined behavior will allow substantial compiler optimizations (if you know, for example, that x+1 is always larger than x because there is no overflow). There are however a lot of cases of undefined behavior in the standard which are outright bullshit, they are "acually OK" and could as well be unspecified or implementation-defined.

What's the difference? Well, implementation-defined means that it's not set in stone what will happen across implementations, but you know exactly (it's documented!) what will happen on your particular implementation. And unspecified means you don't know the outcome, but it's "harmless". Which is true for a lot of operations.

Undefined behavior on the other hand side, means: Abandon ship, everybody save himself, the end is nigh. A lot of people follow the mantra that undefined behavior can just as well format your harddrive. Which is in principle allowable according to the most pedantic interpretation of the wording. Compiler writers often interprete undefined behavior in terms of "I'll just dead-strip the whole function. Or maybe half of it. Or something else.".

Which is just the issue... pointer arithmetic that lands outside the bounds of an array is undefined behavior. I'm not going to argue that this makes 99% of all programs ill-formed (with haphazard results) because every pointer arithmetic bears a result that is outside the bounds of some array (and given the fact that there is argv, there is always at least one other array).

But more importantly, an invalid pointer is entirely harmless on almost all architectures as long as you don't dereference it. Thus, implementation-defined would be a much better match. It would mean "harmless, no effect" just about everywhere, and a trap or something on the one or two architectures with hardware support for checking pointer registers... but whatever it is, there will be no surprise.

Divide by zero? What can happen, realistically? Anything from nothing at all to generating a trap and killing the process. Killing the process is bad, but it isn't demons flying out of your nose!

But since it is undefined behavior, any program that makes a division which doesn't check the divisor for zero could legitimately format your hard drive. Or, the optimizer could just dead-strip that entire function because you don't check for zero (although maybe you know that zero is not possible).


Which is just the issue... pointer arithmetic that lands outside the bounds of an array is undefined behavior. I'm not going to argue that this makes 99% of all programs ill-formed (with haphazard results) because every pointer arithmetic bears a result that is outside the bounds of some array (and given the fact that there is argv, there is always at least one other array).

... what? not some array, the array that is involved in the pointer arithmetic expression! And while that specific part of the standard seems arbitrary in light of the modern, unified, fully byte-addressable memory model of today's architectures, it makes more sense when you view it in the context of segmented memory architectures, where in C you still only have, say, an int* pointer type, but you could have two int arrays in two different memory segments, and it's just not possible to meaningfully, say, subtract the two array pointers, or add an integer to one array to reach the other somehow; with this in mind it makes sense to not have distinct arrays be able to interact in any way (not that most code does this anyway)

EDIT: I think I see your misunderstanding now; the standard states that a pointer not otherwise part of an array may be treated as a one-element array in the context of pointer arithmetic (it is actually very clear on that point)

I agree some aspects of undefined behaviour can seem punishing in that a meaning could have been assigned to the operation that everyone would have been happy with and it would have made life much simpler. But, these things were decided upon a long time ago, and in many cases there were historical reasons for why the standard is written a certain way.

“If I understand the standard right it is legal and safe to do this but the resulting value could be anything.”

People easily complaint about C++, but barely are able to propose and argue solutions about them (especially without smashing the purpose of the language).

"Recursion is the first step towards madness." - "Skegg?ld, Skálm?ld, Skildir ro Klofnir!"
Direct3D 12 quick reference: https://github.com/alessiot89/D3D12QuickRef/

People easily complaint about C++, but barely are able to propose and argue solutions about them (especially without smashing the purpose of the language).

Well... people did sit down and write/propose C++03, C++11, C++14, C++17, boost, and CppCoreGuidelines.

Yes, but those people are the ISO IEC committee. I was not talking about them of course ( :

"Recursion is the first step towards madness." - "Skegg?ld, Skálm?ld, Skildir ro Klofnir!"
Direct3D 12 quick reference: https://github.com/alessiot89/D3D12QuickRef/

People easily complaint about C++, but barely are able to propose and argue solutions about them (especially without smashing the purpose of the language).

Especially when the solutions are not well-received or even hard to maintain tongue.png. I seriously think someone should re-think C++ from scratch instead of driving away into another language.

There should be no more attemp anymore to keep backward compability (in example "template" vs "typename"), most times keeping compability introduces ugly syntax and complexity both on compiler and client side.
The resource management should be always explicit with nice syntax to give away ownership (without resorting necessarily to smart pointers or move semantices


int *a = new int[15];

function_call( give a);



function_call( int * a ){

  // do something.

  IF( MINE(a)) //link time directive => act as a different overload of function_call when
               // "a" ownership is gave away. (when no "MINE" overload present, warn about "a" not disposed.
     delete [] a;
  
}

and get compiler warnings if a owned resource is not disposed (including allocated memory) with no GC at all, templates can be made much simpler and we could get syntax for certain features provided by SFINAE and a decend module system.

Also the language should be thinked for supporting better debuggin, exceptions (I mean no issues like DWARF vs SJLJ that force users to target 2 different binaries at least with GCC), and in general better integration with code-assist tools so that syntax completion becomes easier and faster.

Link time conditional statements could be used to avoid writing a lot of duplicated code, the optimizer will just decide if resolving into 2 different functions or just generate hidden boolean parameters (also that would allow iteratin FOR_EACH over pointers without having to passing manually the size and enable native guards to prevent buffer overflows if wanted).

Instead of writing 2 constructors it could be nice if "moved stuff" has a more usefull behaviour (moving is just copying of all values and poitners and automatic ownership transer, the object from wich we moved should just fallback do a "deafault constructed" object that allow in many cases to avoid writing a 2nd copy constructor and a 2nd copy operator).

I also want better module system even for templates. If I include "std::Vector" (I would not call it "vector" but Array or DynamicArray) I do not want to pollute my namspace with all side included headers of std::vector. The header of "std::vector" should just be a interface, not the whole implementation

Here's a possible example of missing ownership.


int main(){
  int *a = new int[15];

  function_call( a); //thanks to link-time flags compiler know "function_call" will use "a" un-initialized and is able to dispose it. 
                    //seriously no introspection needed at all, just look at dependencies flag when linking and with ability to set a 
                    //compiler flag to stop re-linking when behaviours of subroutines changes 
                   //(in example a subroutine of function_call may initialize "a" after a code change
                    // "a" but we don't want that to go unnoticed so we could examine better the situation)

  return 0;  
}

/*COMPILER WARNING:: "a" is not disposed (file:line), "function_call" is able to dispose it if ownership given
 or instead you must delete "a" before exiting the "main" scope. 
  COMPILER WARNING:: "a" is not initialized  when first used inside "function_call".
  Linker will now assume "main" want "a" to no be initialized inside "function_call"*/

//after a code change:

/*ERROR:: code previously compiled with the assumption that "a" is not initialized inside "function_call" may now be broken because of
    "subroutine_1" now initializing "a". You can recompile "main" or just suppress this error with "-AllowsUpwardLinkFlagPropagation" */


Example to enforce ownership transfer and no initialization


function_call ( noInit give a);

This topic is closed to new replies.

Advertisement