8 ms·
As my first language, I've yet to experience another language that genuinely lets me be as creative as C++. There are hundreds of ways to implement and optimiz
by arberx 4y ago
As my first language, I've yet to experience another language that genuinely lets me be as creative as C++.
There are hundreds of ways to implement and optimize similar functionality that I feel like an artist writing in it. This, of course, comes with many downsides.
- Chabsff 4y agoAnd this half of my main beef with the language. To be clear, I absolutely agree that this aspect of it is wonderful, but it is a double-edged sword, and the edge pointing at the programmer's face is very sharp. The second half of that beef is that most C++ learning journeys bring the learner up to the point of creativity without addressing concepts like Strict Aliasing which are not useful until you start getting "creative". That's big a problem because code containing strict aliasing violations (and/or any of a myriad of other UB) is often fundamentally broken while giving the illusion of behaving as intended.
- synergy20 4y agoor use -fno-strict-aliasing and profile the code to lift it for bottleneck code carefully? linux kernel does not allow strict-aliasing for example. but I agree yes all c and c++ course should teach strict aliasing. in modern c++, also teach RAII and unique_ptr, combined they can eliminate so many memory safety problems.
- Chabsff 4y agoC++ without strict aliasing is not C++ anymore. Furthermore, that was just an example. There are other such concepts, such as the illegality of accessing memory as anything else than raw bytes or the type of the object that resides there (which also applies to union fields). Another one would be the fact that signed integer overflow is UB. C++ is so tightly bound to memory layouts that once you start getting a solid mental model of what's going on, it's really easy to come up with legitimately neat and creative ideas that work very well and fast on a test bench environment without any complaint from modern compilers, but are fundamentally broken. I feel we do not do a good job at equipping up-and-coming programmers so that they can benefit from that mental model without writing code that only works out of sheer luck. In the extreme, you can write code that does stuff like dynamically retype an object by swapping out its vtable when vtables are not even thing as far as the language is concerned. It's astounding what it sometimes feels like you are capable of doing in C++.
- synergy20 4y agoI understand compiler likes strict aliasing for performance optimizations, use it carefully but _only_ at bottleneck-ed areas seems like a good trade-off between safety and performance, why without-it is no c++ anymore? why do I need do pointer aliasing randomly?
- Chabsff 4y agoStrict aliasing is what prevents compilers from having to sprinkle Load-Hit-Stores all over the place. This is not a marginal performance difference, and doubly-so for CPUs with deep execution pipelines. It also has massive code-reordering implications. In any case, Strict Aliasing is not a flippant rule that compilers like to apply. It's a formal component of the ISO standard that C++ is. You can chose to make a deal with a compiler (via a flag) to compile otherwise non-compliant code in a consistent way, but that does not change the fact that the code is non-compliant.
- kazinator 4y agoThere are ways of coding coding in C which makes those optimizations unnecessary. The basic idea is: don't load your block of code with lots of pointer dereferences. Load the values you need into local variables. Don't proliferate common expressions which dereference the same pointer to get at the same value. Consolidate the assignments through pointers. Don't do this in three places: (*ptr)++. Have that value in a local variable var, and do var++ in three places, then assign it *ptr = var. Even without no-strict aliasing, a compiler can still assume that local variables whose addresses are not taken are not the targets of any pointers. In C (99 or later), you have restrict also. restrict is independent of strict aliasing, because it's not based on type. Furthermore, speaking of restrict, aliasing between like typed objects matters for optimization. It's good and well to optimize based on the idea that a double * cannot be aiming at an object whose declared type is long. But it's insufficient, because code that is manipulating double * pointers is likely working with objects of type double which could be the targets of those pointers. So without some combination of tight coding and possibly using restrict, you will end up with those load-hit-stores in all sorts of code. This code for removing from a linked list: node->prev->next = node->next; node->next->prev = node->prev; still isn't as good as this: node *next = node->next; node *prev = node->prev; next->prev = prev; prev->next = next; The problem is that the common expressions can't be eliminated based on aliasing, and the aliasing is between like types, so strict aliasing doesn't help. With gcc (x86) I get: movl (%eax), %edx movl 4(%eax), %ecx movl %ecx, 4(%edx) movl 4(%eax), %eax movl %eax, (%eax) versus: movl (%eax), %edx movl 4(%eax), %eax movl %eax, 4(%edx) movl %edx, (%eax) We shaved off an instruction through tighter coding, and IMHO (in this case) improved the readability also. That was gcc 7 on Ubuntu. The result is exactly the same like what I saw under gcc 2.7.x a quarter century ago. How about gcc 11, x86-64? I should probably be using godbolt, but anyway, also five instructions down to four: movq (%rdi), %rax movq 8(%rdi), %rdx movq %rdx, 8(%rax) movq 8(%rdi), %rax movq %rax, (%rax) vs: movq 8(%rdi), %rax movq (%rdi), %rdx movq %rax, 8(%rdx) movq %rdx, (%rax) (I think in this particular case the compiler could do a better job because even if the assignment "node->prev->next = node->next" clobbers the value of "node->next" due to the nodes being aliases, the assignment can only clobber it with the value that node->next already has! The compiler doesn't analyze it that far though.) C was designed from the start as a language which the programmer does the optimizing, and regardless of the advancements in compilers, that has not been entirely eliminated. How you write C still makes a difference, even at the microscopic level of individual statements and expressions, not just the level of overall program organization and use of algorithms. If you write tight code, you can turn off strict aliasing optimizations globally and it won't matter. But you don't have to do that globally. You may be able to confine your type punning hack in its own source file, and just turn it off for that file. (Or may be even on a finer granularity if you have such compiler support.)
- rramadass 4y agohttps://blog.sigplan.org/2021/11/18/undefined-behavior-deserves-a-better-reputation/ https://blog.sigplan.org/2021/11/18/undefined-behavior-deser...
- Chabsff 4y agoJust in case you posted this as a counterpoint to what I'm saying... This blog effectively agrees with my position: Undefined Behavior is just "Stuff the compiler is allowed to assume never happens". And C++'s set of Undefined Behaviour is a fundamental part of what makes it valuable. The issue I have is not with them, but rather with the fact that it's too easy to become competent, if not talented, way past the point where one should take them into account without having to even know of their existence.
- rramadass 4y agoDiscussion here: https://news.ycombinator.com/item?id=32908724 https://news.ycombinator.com/item?id=32908724
- forgotpwd16 4y ago>There are hundreds of ways to implement and optimize similar functionality that I feel like an artist writing in it. Perl? Can even write poetry in it. https://docstore.mik.ua/orelly/perl/prog3/ch27_02.htm https://docstore.mik.ua/orelly/perl/prog3/ch27_02.htm
- volsa_ 4y agoAs someone who tried learning C++ a few years ago this is (personally) my biggest gripe with the language and this gif[0] perfectly sums it up. Gave up and started learning Rust instead, which I'm very happy about in hindsight. [0] https://twitter.com/timur_audio/status/1004017362381795329 https://twitter.com/timur_audio/status/1004017362381795329
- alfiedotwtf 4y agoSame here... I got through Stroustrup and thought I'm not smart enough to write this error free. Been happy with Rust ever since.
- cyber_kinetist 4y agoIt’s all fun and games until you find the need to unsafe Rust, which opens up a bunch of eldritch monstrosities that are sometimes even harder to tame than C++. The happiness people get from Rust was achieved by the incredibly hard work of library developers who had to deal with all sorts of complex semantics under the hood to make safe Rust safe. The knowledge gap between library user and library writer is even worse than C++, where most people do not feel comfortable enough to actually write low-level libraries (custom data structures, bindings from C, optimized routines) in Rust. (At least PL people are inventing things like stacked borrows to make writing unsafe Rust easier… but it’s still not easy.)
- unrealhoang 4y agoon the other hand, that makes Rust's learning curve way way more gradual than C++. An intermediate programmer with 3 months of Rust experience can reliably and confidently cobble up the libraries together to contribute to the project. Whereas such programmer will be a heavy burden (for other seasoned devs to guide/review/feedback) in a C++ project. Also, building low-level libraries that work correctly, efficiently, relatively-safely in C++ is NOT a simple feat, and requires years of experience (on API design). With unsafe Rust you can just fuzz/brute force out the unsoundness with the safe API.
- 4y ago
- a3w 4y agoHow do you fell about the pitfalls, like "if you inherit from a class, make sure the base destructor is virtual, or you just added a memory leak" Some books on C++ teach you those, but I give up very early on learning languages with non-obvious expectations that offers few safeguards.
- Chabsff 4y agoThat's not even particularly good advice, which just goes to show how subtle the pitfalls are. The rule really should be "If it is at all possible to delete instances from a pointer to the base class, then you need a virtual destructor". The overhead of adding a virtual destructor to a base class with no other virtual functions is far from negligible in many cases, so the distinction is meaningful (though modern guidance tends discourage non-polymorphic inheritance in the first place anyways). The same thing goes for the good-old "Do not use raw pointers." The rule is actually: "Do not use raw pointers with implicit ownership semantics attached to them."
- nyanpasu64 4y agoIn retrospect I wish C++ used fat pointers rather than inheritance for virtual dispatch, like Rust but where interfaces/traits can have fields (Rust traits don't have fields and are less powerful than C++ base classes). This prevents accidentally overriding a base class method (eg. QWidget) by declaring a subclass method of the same name, eliminates the need to add a vtable pointer to object instances which you never cast to an interface pointer, and eliminates the need to juggle pointer offsets when performing multiple inheritance method calls, which can cause UB when performing C-style pointer casts and makes decompiling the code a nightmare (possibly fields in traits will add this requirement again, I'm not sure).
- rramadass 4y agoThat is because C++ is not taught properly. It is not just a collection of syntactical features but a multi-paradigm language (i.e. supporting Procedural, Object-Oriented, Generic and Compile-time) where a feature is best suited to a particular paradigm and can also be used to express different concepts. Once you grasp this, things become quite clear and you understand how to design in C++. To take your example; the same C++ "class" syntax can be used to express different concepts; a) A user-defined value type eg. ComplexNumber. Therefore the dtor need not be virtual since the class is not designed for inheritance. b) A pure interface used for "Interface Inheritance" i.e. all methods are "pure virtual". Therefore you need a pure virtual dtor. Java has the "Interface" keyword for this. c) A class designed for a type hierarchy used for "Implementation Inheritance". Therefore you need a virtual dtor. d) A class can also be used as a simple namespace eg. all static members; not designed for inheritance and thus no dtor needed.
- latenightcoding 4y agodon't worry Perl will let you be 100 times more creative
- medo-bear 4y ago> As my first language, I've yet to experience another language that genuinely lets me be as creative as C++ common lisp is cpp's way more talented brother that got addicted to lsd and became a hippy
- mattlondon 4y agoTry modern pure JavaScript. It's like floating through air. An absolute joy to code in. Of course if you want to talk about performance and stuff then JavaScript is not comparable, but that is not the point
- layer8 4y agoThe lack of static typing will be a non-starter for many.
- colejohnson66 4y agoTypeScript
- kazinator 4y agoThere are hundreds of ways to do something, each one more importunely verbose and loaded with extraneous syntax than the next.
- intelVISA 4y agoWhat eldritch horror am I going to unleash thanks to g++ today? But I agree, C++ is the paintbrush of coding and I grow tired of my Js crayon prison.