Return value optimization is absolutely allowed in C, and ~all compilers do it. It's not commonly talked about because in C, there can be no side-effects when constructing or copying a struct, so there's nothing to discuss. It just happens automatically and there's no observable change in program behavior.
From what I can see, the example you list there is just a missed optimization in the C compilers you tested. It's absolutely allowed, and clang trunk is happy to do it in C-mode, for example.
There could be some more fundamental differences due to the requirement that different objects require different pointers that prevent some optimizations.
C compilers can do copy ellision when inlining code as long as the result is identical. That's not quite the same thing as RVO, which deliberately relaxes the notion of "identical result" such that it's possible to write C++ code to detect whether or not RVO was actually performed. That's not possible in C (or shouldn't be).
It's possible that I'm missing something, but I believe that the result is necessarily identical in C, because there can be no observable side-effect of a struct copy in the C abstract machine.
I tried to find an example that contradicts what you said, but I can't find any. Here's an online example which can be compiled as both C and C++ and I obtain exactly the same assembly using both clang and gcc. Maybe somebody can tweak this example to force the C compiler not to perform the optimization.
See my reply to the parent poster for an example where RVO applies in C++ but not C, leading to better code in the C++ case. The key is to let the address of the objects involved escape: otherwise "as if" will let C (and C++ even without RVO) optimize away the copies anyways, since they are not observable in that case.
No, it is not necessarily identical in C, because even though you don't have constructors or destructors, you can make an address comparison to check whether two objects are identical.
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!
No C doesn't have RVO in the C++ sense. Sure C can optimize the copies implied by "returning values" in the same way they can optimize other copies or anything at all, if the observable behavior is the same, due to "as if" - but that's not RVO (as it is discussed in C++). It doesn't even need a special name: it's just plain old optimization.
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++".
I find D to be a good tradeoff between C++ and Rust. I can't stand not having compile-time evaluation, code generation and reflection in Rust and C++ is just too slow to iterate with.
Working with Python lately I very much got to like the interactive REPL and its immediate feedback.
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.
Compiling the hello world with -betterC yields a binary of size 8.2K. Seems small but when your microcontroller has 8K of program space that's just untenable. Of course those small micros are getting rarer nowadays when a large AVR and ARM micros are cheap, but there is still an entire range of minimal microcontrollers that are extremely limited in resources.
Agreed, I actually really like C++ nowadays. I usually tinker with compilers in higher-level languages, but my current project I'm using LibJIT, and wanted to go all-native.
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.
Why not implement standard data structures? Or system programs ( ls, ping, etc )? Or a compiler ( a subset of C perhaps )? Hell a basic http server or sqlite clone? There's an endless list of projects you could do in C++ if you really wanted to.
Please please please can we get rid of headers and have modules? I find it super annoying having to specify things half in one place and half in another. Unfortunately, there still seems to be a bit of disagreement on the implementation among the standards committee.
Certain IDE vendors could solve this if they really cared about UX. Not by implementing modules, but by efficiently scanning vast directories for versions of headers, and arranging them in a visually navigable manner (you know, actually being a useful IDE). The last time I used one, the OS was incapable of even searching folders. Partial string matching of filenames was apparently too complex for it to handle.
If you have your IDE properly configured with the include dirs needed to build, at least MSVC can autocomplete for you. Personally, I don't rely on this, as I do most of my C++ work from the command line using bash, vim & make.
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
well, for instance, in Qt Creator, if you have a type not available you can right click on it and it will propose a list of headers to include it from that it will add in the file.
I actually don't mind header files, but I do mind includes as the mechanism for stitching things together. Also, C++ doesn't have any concept of a package. You get libraries and headers and it's up to you to feed them to get them to the compiler correctly.
Don't forget about the whole "include what you use" problem. There's a static analysis tool for that but of course it's a hopeless proposition for something as powerful as headers (despite no one ever needing that power).
Absolutely There keeps being proposals for C++ standard modules, and it keeps getting punted.
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.
Includes mixed with #ifdef leaks state across module borders and the whole C preprocessor feels like a dirty hack.
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.
Compiling Chromium, the biggest C++ project I know, takes on the order of 8 hours with a somewhat old but quite powerful server. Linking Chromium on my laptop, with 8GB of RAM, required me to increase my swap space from 8GB to 16GB.
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.
What I'd like to have is someone to devise a plan on how to seamlessly progress from C++ to a language with a wholly new and cleaned up syntax. A way to slowly break out of the shell given by current backward compatibility. But at the same time being able to seamlessly interact with the old C and C++ world. Maybe something like "unsafe" in Rust.
Having modules in C++ smells like the connector we're missing in order to accomplish something like that.
It takes all sorts, but I prefer the headers. Each project’s copy only gets upgraded explicitly, the debug info is always there, single stepping always works, there’s never a problem with ABI/compiler option mismatches (and you get the unoptimised version in your unoptimised build), anybody that gets the repo automatically gets all the dependencies too.
Sure... but it would have to be proper-as-in-proper. And what are the chances of that? Seems to be hard enough to even enforce the rule that ``-O3 -g'' is a thing, let alone all the rest :(
You do not really to do that in C. If you are careful, the header only contains the visible interface, which is a list of functions that can be trivially deduced from the non-static functions of the source file. Moreover, the header file is optional, and is never included by the source file.
Yeah. If you don't include the header from the source file you will inevitably end up with differing function signatures in the header and source files because the compiler won't be able to compare the declaration and definition to each other. Such a program may even link.
is there a modern C++ for python programmers? i have ~5 years programming in dynamic/interpreted (python/js) and compiled/gc'd(java/go) languages and i'd like to learn a systems language. i'm reading the rust programming book (and it's really good/easy to grok) but i'd also like to learn C++. the problem is that most books are either too easy (C++ as your first language) or too hard (straight into RAII and templates). any suggestions for learning resources? modern being key here (stuff like auto ptrs etc.)
Been getting back into C++ lately after not using it since college ('02), did not know they were releasing a new edition of Tour of C++, will have to check it out (already read Principles and Practices which was interesting once it got into the meat of C++, and plan to read Myer's effective books at some point).
Worth noting that auto_ptr is decidedly not modern, it was deprecated in c++11 because it was error prone and replaced with C++11's unique_ptr (and shared_ptr/weak_ptr too sort of, although they have a different use case -- unique_ptr really is basically the same use case as auto_ptr).
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.
After dabbling a bit in C++ after college, then purposely avoiding it for about twenty years, I'm getting back into it for work. I'd say my favorite books for coming up to speed were C++ Common Knowledge, Effective C++ (3rd edition), and Effective Modern C++, more or less in that order. Discovering Modern C++, which I haven't finished yet, has not been as enjoyable.
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.
Yes! And be careful and very picky about what you read! To quote a popular Quora answer [1]:
"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.
Probably not quite what you're looking for, as I assume you're looking for a Modern C++ guide for Python developers, but surely the Boost Python source is worth a look for good Modern C++ to interact with Python. Fair warning, the learning curve on understanding or using this library is more like a cliff than a curve. It is very heaving in TMP (Template Meta Programming). That said, if you can make sense of this, you can probably consider yourself a near expert in TMP.
I'm primarily a JavaScript developer nowadays. I used C back in the 80s and 90s but never really used C++. I decided to start teaching myself C++ a few weeks ago. I've been going through "The C++ Programming Language 4th Edition". It only covers up to C++11 but the first 100 pages or so goes through a quick whirlwind overview of the newer "modern" C++. The rest of the book goes more in depth and is a bit basic for most of the text for experienced programmers. So it has both the too easy and too hard parts but at least you can jump around to get somewhere in between. It's not great but it's the best resource I've found so far. I tried some Udemy courses but a lot of them move too slow and assume no programming experience.
> The move constructor is the important bit. Its presence tells C++ that this class can not be copied, only moved
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.
"The implicitly-declared or defaulted copy constructor for class T is defined as deleted if any of the following conditions are true ... T has a user-defined move constructor or move assignment operator" (since C++11)
The committee probably observed that in most cases where people wanted a move constructor, they needed either no copy constructor or a more complicated one (to handle duplicating a resource.) So creating a default copy constructor (a convenience in the common case of a simple structure) wasn't a win.
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.
> In general, always prefer the std::make_* form for smart pointers. For std::shared_ptr it turns two allocations into one, which is a huge win both in CPU cycles and memory consumed.
You also get exception safety because the it's not evaluated in an unspecified order.
I don't see the problem here. The code you quote is correct and guaranteed to allocate the minimum amount of memory for which the hardware can support the requested functionality.
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.
Well, there's not a lot of context to say whether there are more shared counters. That said, I overlooked the MAP_SHARED, and the code actually got itself in non-portable land. Not all atomics are actually atomics. IIRC, at least ARM doesn't have 64-bits atomics, and using atomics on such platforms means using locks. Which are very likely to not work properly in shared memory.
"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.
That SmartFP class is not safe to use, it doesn't implement or block the copy constructor so you might get two objects pointing to the same FILE*. (rule of five vs rule of zero)
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.
81 comments
[ 3.5 ms ] story [ 108 ms ] threadhttps://godbolt.org/g/7Ewun7
Hah, sounds like you nailed the emergent philosophy of C there... :-)
For example the following code:
https://godbolt.org/g/ikD6bi
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++".
https://news.ycombinator.com/item?id=17309654
https://ds9a.nl/articles/posts/cpp-2/
https://ds9a.nl/articles/posts/cpp-3/
https://ds9a.nl/articles/posts/cpp-4/
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.
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.
[1] https://github.com/nothings/single_file_libs
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 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-...
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.
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.
You also get exception safety because the it's not evaluated in an unspecified order.
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.
"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.