Re "When we know the dynamic type", I made a similar assertion on HN years ago, and of course it turned out that there's a weird wrinkle:
If the code in your snippet is expanded to:
Derived d;
Base *p = &d;
any_external_func(); // Added
p->f();
where any_external_func() is defined in some other translation unit (and, I'm now fairly sure, Derived's ctor is also defined in another translation unit, or it transitively calls something that is), this would seem not to affect anything -- but in fact the compiler must not assume that p's dynamic type is still Derived by the final line. Why? Because the following insane sequence of events might have happened:1. d's ctor registers its this pointer in some global table.
2. Using this table, any_external_func() calls d's dtor and then overwrites it in place with a fresh instance of Base using placement new, which replaces everything, including its vtable, meaning that p->f() should call Base's version, not Derived's.
(It might be UB to call placement new on static or automatic storage holding an in-lifetime object, I don't know. If so, the above construction is moot -- but the closely analogous situation where we instead dynamically allocate dp = new Derived() still goes through, and is nearly as surprising.)
A pedantic Pete would mention C++ has member functions.
Go Pete!
The choice (in C++ and then Java and C#) to pretend that these "member functions" are somehow part of the data structure also more or less guarantees that some students will end up confused about what's actually in the physical representation. Of course the "git gud noob" mindset of C++ doesn't see that as a problem :/
C++ has plenty of warts to complain about, however complaining for the sake of complaining usually doesn't win attention.
I think that C had members, so members that are functions just became that. But then again, members that are types are usually called nested types.
Example, Turbo C++ manual from 1990, 8 years before the standard.
It is no different from several languages having their own meaning for functor, e.g. it has three different meanings across C++, Haskell and Standard ML/OCaml.
Or what mixins mean between several OOP languages.
Clang relied on checking the address of a function pointer in the vtable to validate the class was the type it expected, but it wasn’t necessarily the function that is currently being called. But due to ICF two different subclasses with two different functions shared the same address, so the code made incorrect assumptions about the type. Then it promptly segfaulted.
It seems that for devirtualization GCC has a warning option -Wsuggest-final-types which is supposed to tell when devirtualization fails in link-time optimization. Not sure how reliable that is, or whether it will produce gobs of unhelpful warnings. Maybe it could be combined with some kind of hint that we want this particular call to be devirtualized, and don't care about calls without the hint.
In such cases I prefer using std::variant instead of inheritance, but this works only if all possible types are known ahead of time.
A vector of variants still wouldn't be optimal. Better to use something like
https://www.boost.org/doc/libs/latest/doc/html/poly_collecti...
Transpose.
To be fair, in C++ that has been the ruling rule of thumb for since ever.
That advice is often given, but I think few C++ programmers follow that to the letter, because you often know beforehand where your performance bottleneck is, and using some simple heuristics upfront may mean you won’t have to spend time benchmarking and refactoring later.
> If you're always going to the same place, the branch predictor can quickly figure that out even if the compiler can't.
But if the compiler knows which function is called, it can choose to inline it, can’t it? For short functions such as getters and setters, the difference can be huge. “call, move, return” instead of “move” is (ballpark) a factor of three, even ignoring cache pressure. The compiler may even choose not to construct a class instance at all, and keep its state in registers.
This year, I’ve finally taken the plunge to properly learn Rust (I’ve used it for little things over the years, but never for anything particularly extensive) and one thing that jumped out at me is that you don’t need to think about it, because Rust makes it explicit: everything is statically known unless you explicitly ask for it to be virtual.
[edit: since it wasn’t clear, I mean polymorphism in rust is static by default while in c++ static polymorphism requires relying on the compiler or using templates, otherwise polymorphism is via virtual]
It’s was a little annoying at first because some things don’t just work automatically, but once I got used to it, it was wonderful to never have to think about when the compiler might do something. You also don’t need dynamism most of the time.
I still like tinkering in C++, but I do find you need to know too much about compiler heuristics.
This is true in C++ also...
In rust, you can have traits without dyn.
That is, static polymorphism is the default in rust, while in c++ you must jump through hoops for it (eg, see the excellent EnTT’s static polymorphism companion library).
I also have my share of hobby coding in Rust.
In the OP, there are ten test cases. Some devirtualise. One only on clang, another only on GCC. In Rust, polymorphism is always "devirtualised" unless I say "no, actually, make this dyn", and in many cases, that's actually perfectly fine. It feels like the rational default, you only pay for the dynamic support when you need it. I don't dislike C++, but sometimes it gets exhausting having to remember all the situations where the compiler might do this or that, or I have to write far more complex code via templates or other techniques to get the behavior I want (sure, its been made easier with recent C++ versions, I admit I'm still on C++20 and even then not all of it; originally because compiler support was patchy, now because I just haven't used C++ as much lately). Its that difference that I thought was interesting.
To each their own, but yeah, your experience matches mine.
It is a book that plenty think it has Java in it, no it has Smalltalk and C++ and was published before Java came to be as you point out.
The decade was about the book publishing date, rather all the C++ frameworks that came to the market since CFront 2.0 was made available in 1989, and yes it was exactly 7 years, if being pedantic.
First of all they were extremely buggy back then, as anyone that actually used Stepanov's original implementation, from HP with docs hosted at SGI, will remember.
Secondly, Turbo Vision, OWL, VCL, MacApp, PowerPlant, Tools.h++, RogueWave Collection Classes, MFC, wxWidgets, Gtkmm, Qt, PÖET, COM/OLE/ActiveX, CORBA, Copland, BeOS predate Java, not the other way around.
It isn't writing C++ as Java, it is writing C++ as it was common between 1990 and 2011, before C++11 and other programming techniques took over in the C++ world, highly influenced by Andrei Alexandrescu's book "Modern C++ Design", published in 2001.
CUDA frameworks, V8, LLVM and GCC, you name it.
Most C++ code being written today is the same code that has been here for years. What compels you to believe things are rewritten on the spot? I mean, I personally know of flagship C++ projects that are still stuck in C++14 and earlier. Why would this change?
Perhaps people should pay attention to the fact that C++ goes way out of its way to be backwards compatible.
That's fine, but definitely something that does not happen in professional settings. You simply don't look at a codebase with a few million LoC and decide on a whim that you are going on to, say, replace all raw pointers with smart pointers. That is not going to happen. That definitely did not happen at all.
May I remind you that Google's C++ coding guidelines discourage exceptions because they do not have the resources to refactor all their code to be exception safe? Google doesn't have the man power but you do?
I don't have a dog in the race, but this is the first time I ever heard anyone express this opinion. Google's C++ guidelines are the first thing that critics cite when complaining about exceptions, and they do so as an example and not as something "bad".
They also have nothing to do with scale, really. That's a puzzling statement. I have no idea what compels you to make that claim.
If you tell a Product Manager that you want to upgrade a compiler just because, and all it will take is a month or two of work including full regression tests on all target platforms and collaboration from all teams, that isn't an easy sell.
Interesting study though and I wonder if it has at all improved since.