Modern C++ for C Programmers: part 5
ds9a.nl
ds9a.nl
Hah, sounds like you nailed the emergent philosophy of C there... :-)
For example the following code:
where the address of the caller's variable that will take the return value is passed along to the function returning it. Within the callee, f1 and f2 must be different objects in C, but not in C++.
All decent compilers compile this in C++ with only a single object, so sink() will receive identical objects. Most compilers "deoptimize" this in C since sink cannot receive identical objects: two objects are created so f1 and f2 have different addresses, and a copy is performed from f2 to the return value f1.
Due to a bug, clang mis-compiles this and applies the C++ RVO to C. So you maybe you are right: you can get RVO in C, but only due to a compiler bug!
RVO is a very specific optimization that has its own language in the standard (which is notable since the standard is pretty much silent on the vast majority of optimizations) and it is special precisely because it allows the optimization to occur even if it changes observable behavior. C does not have it, and compilers must compile the some code more conservatively because of it: e.g., if the address of return values escape in the caller and/or callee, a C compiler must ensure that the program always observes distinct addresses for distinct objects in the source, but in C++ one is free to observe that two apparently different objects are the same.
Now, sure, at least 90% of actual RVO is about eliminating constructors and destructors with side effects - which C doesn't have and so RVO simply doesn't apply most of the time: but one shouldn't conclude that "C has RVO", rather "C mostly doesn't need RVO, and in the cases were it could use it, it is not allowed and must be more conservative than C++".
Can't explain why, but for C++ projects, I intuitively remember what paths I need for headers, but for a C# project, I rely upon autocomplete as a crutch. Maybe it's just how much more time I spend doing C++ work and the time I spend with the libraries I use vs the relatively small time I spend with C#.
You're misunderstanding me. I'm saying the IDE should discover headers for me. MSVC refers me to a Byzantine series of clicks, drop downs, text input, and OK buttons. That interface hasn't changed for 20 years
A proper modern UX would scan for valid headers in a dir, then allow me to choose which ones to "import" into the workspace. Not make me hunt for them through an antiquated and inefficient UI
Having a reasonable module system, and hell even some lovely tools for migrating sane projects over to it, would make me much happier with the language.
For what it's worth, Clang has experimental support for C++ modules.
The trouble being actually indexing the contents of any given module and trouble with template instantiation.
What a header actually does depends on compiler flags, #defines from other files and then the order in which you include things as one header file might define something that changes the behavior of another header file. The compiler loses state in between C++-files and caching precompiled header files was still a joke the last time I've checked.
That leads to the problem that every single C++ file needs to include a few hundred thousand lines of nested headers and compilation takes forever. Add in some templates and you get a lot duplicated code in the object files that needs to be weeded out by the linker. Sometimes it's even suggested to put all your classes into one single big C++ file and only compile that in order to get your compilation time down.
Having proper modules would do so much good!
Going away from C's linker and header file-model, which really doesn't work well for C++, to something based on a proper module system, would be amazing.
Having modules in C++ smells like the connector we're missing in order to accomplish something like that.
That single file is the substitute for a package that you can simply put somewhere, include it and it works.
So the substitute is much better than the package!
In C++, yeah, this language sucks.
That is totally opposite to my experience. Example from Linux:
https://github.com/torvalds/linux/blob/master/kernel/smpboot... https://github.com/torvalds/linux/blob/master/kernel/smpboot...
I actually think I forgot how to program in C. It lacks almost every data structure I'd deem useful to getting complex and mixed problems solved quickly. C++ provides many things and is useful if you are in a tight spot or you want/need the speed.
If Rust keeps evolving at a quick pace I think I'll look into it a lot in the future.
D seems to me like a good thing that is better in many ways but has some critical drawbacks for me like not working on many microcontrollers (an area where C++ really shines). On the other hand it isn't radical enough to really dive into it.
I've had a blast with OOP in C++, believe it or not, and writing my recursive descent parser, symbol table, and analyzer felt like working with a higher-level language.
I figure you didn't mean to call it out specifically and more meant general smart pointers, but just felt I'd let you know that searching for that terminology may lead you astray.
Not only that -- it was removed in C++17!
I wouldn't get too hung up on skipping straight to "modern" C++. Yes, C++11 is a game changer, but the vast majority of what you'll find in the classic recommendations is still worth knowing. Things like type deduction, smart pointers, and move semantics won't be hard to pick up once you've got the basics. Although I haven't read it, Accelerated C++ sounds like a good fit for your situation.
"If you really want to learn C++, I advise against any and all online tutorials and against most books. Without any kind of quality control, many professional book writers published many awful C++ textbooks that may be easy to read and sell well, but teach lies. Stick with the community-verified book list available at StackOverflow"
In other words, use the StackOverflow list to choose the right teaching material [2]. A Tour of C++ (Bjarne Stroustrup) has been a good refresher, with C++ Primer (Stanley Lippman, Josée Lajoie, and Barbara E. Moo) as the primary reference for further clarification. Also use GeeksForGeeks and LeetCode practice problems.
[1] https://www.quora.com/Which-is-the-best-book-for-learning-c+... [2] https://stackoverflow.com/questions/388242/the-definitive-c-...
You also get exception safety because the it's not evaluated in an unspecified order.
"Such magic does not come for free however. If we look ‘inside’ a std::shared_ptr, it turns out it carries a lot of administration. First there is of course the actual pointer to the object contained. Then there is the reference count, which needs to be updated and checked atomically at all times. "
My impression was that simply dereferencing, which is going to be most of the use of it, is not checking that counter at all. Only things like assignment modify it, which should be more rare.
The article does mention this, but only very briefly and only gives one of the two solutions without much explanation behind and it isn't exactly easy to find it in the article even if you know what you are looking for. Search for deleter in part 2 of the article to find it.
auto ptr = mmap(NULL, sizeof(std::atomic<uint64_t>), PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS, -1, 0);
g_counter = new(ptr) std::atomic<uint64_t>();
waw, a 4k integer (or even more, depending on the platform).It passed sizeof(std::atomic<uint64_t>) as the length parameter and mmap() decided to give us a larger page (which may or may not be 4k) for its own very good reasons, namely that the overhead of keeping track of hyper-granular memory pages would cost more than it would save.
For a program that needs at least one shared counter, it's not possible to allocate less than one page of shared memory, regardless of if its implemented in C++, C, or whatever language -- it's a hardware level decision. If MMU designers could still provide 512 or smaller pages with the same performance as 4k pages they would certainly do so. Nor is it reasonable to keep track of memory flags like MAP_SHARED at a more granular level than a single page.
If the program did need more than one shared counter, it could instead use something like std::array<std::atomic<uint64_t>, 512>. In other words, there's no reason why it would have to allocate 4k for every shared counter -- there's zero inherent overhead. It appears as if this particular program only needed 8 bytes, request 8 bytes, and had its request satisfied in the best way the hardware could allow.
Is that true? I thought move ctor enabled move semantics, but if you pass by value the copy ctor was still called (except in places where RVO makes sense). And I thought to disable copying you made the copy ctor = delete.
So if you define a move constructor and still want your class to have a copy constructor, you'll have to explicitly define the copy constructor, too.
A::A(const A &other) = default;https://en.cppreference.com/w/cpp/language/copy_constructor
But there's no harm in explicitly deleting it, and it helps document the intent.
Perhaps if you were designing C++ from scratch without legacy and had a more Python-like mindset, you'd require an explicit "= default" rather than complicated rules about whether a default is provided for you or not.