11 ms·
Modern C++ for C Programmers: part 5
- stephencanon 8y agoReturn 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.
- ajross 8y agoC 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).
- stephencanon 8y agoIt'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.
- SidiousL 8y agoI 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. https://godbolt.org/g/7Ewun7 https://godbolt.org/g/7Ewun7
- BeeOnRope 8y agoSee 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.
- 8xde0wcNwpslOw 8y agoDon't quote me on this, but I think padding bytes might have different values in certain cases.
- a1369209993 8y agoI'm fairly sure it's always undefined behavior to observe those differences, so while it's possible in practice, it'd never work in theory.
- e12e 8y ago> while it's possible in practice, it'd never work in theory. Hah, sounds like you nailed the emergent philosophy of C there... :-)
- BeeOnRope 8y agoNo, 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. For example the following code: https://godbolt.org/g/ikD6bi 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!
- stephencanon 8y agoAh, yes, that's a nice example.
- jcelerier 8y agoNo, C has much less RVO allowed than C++. I provided an example of this here the last time : https://news.ycombinator.com/item?id=17216250 https://news.ycombinator.com/item?id=17216250
- stephencanon 8y agoFrom 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.
- petters 8y agoThere could be some more fundamental differences due to the requirement that different objects require different pointers that prevent some optimizations.
- BeeOnRope 8y agoNo 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++".
- rrmm 8y agoPlease 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.
- mikec3010 8y agoCertain 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.
- hermitdev 8y agoIf 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#.
- mikec3010 8y ago> If you have your IDE properly configured 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
- jcelerier 8y ago
- jwilk 8y agoPrevious parts: https://news.ycombinator.com/item?id=17309654 https://news.ycombinator.com/item?id=17309654 https://ds9a.nl/articles/posts/cpp-2/ https://ds9a.nl/articles/posts/cpp-2/ https://ds9a.nl/articles/posts/cpp-3/ https://ds9a.nl/articles/posts/cpp-3/ https://ds9a.nl/articles/posts/cpp-4/ https://ds9a.nl/articles/posts/cpp-4/
- 52-6F-62 8y agoThanks! I missed this the first time through
- ttul 8y agoI wish I had a project that warranted C++ right now. It’s a magnificent language.
- csears 8y agoWhat would make a new project a good fit for C++?
- andrepd 8y agoPerformance critical.
- std_throwaway 8y agoAlso if Rust is not cutting it yet in your specific niche.
- jeremiep 8y agoI 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.
- std_throwaway 8y agoWorking 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.
- 8y ago
- mlevental 8y agois 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.)
- swaroop 8y agoHave you tried "A Tour of C++"? https://www.safaribooksonline.com/library/view/a-tour-of/9780134998053/ https://www.safaribooksonline.com/library/view/a-tour-of/978...
- tsaoyu 8y agoOrdered a paper version on Amazon UK one month ago. Does anyone get the book already?
- runevault 8y agoBeen 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).
- yoklov 8y agoWorth 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.
- thestoicattack 8y ago
- mikec3010 8y ago> 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.
- ahubert 8y ago(author here), it is true, try it. Sample code is on https://github.com/ahupowerdns/hello-cpp/blob/master/move.cc https://github.com/ahupowerdns/hello-cpp/blob/master/move.cc
- mikec3010 8y agoThanks, and sorry for not just trying it. I'm on mobile and our workstations here aren't cpp11 capable.
- datamart 8y agoThe implicitly-declared copy constructor is deleted if there is a user-defined move constructor. See: https://en.cppreference.com/w/cpp/language/copy_constructor#Deleted_implicitly-declared_copy_constructor https://en.cppreference.com/w/cpp/language/copy_constructor#... 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.
- mikec3010 8y agoThat makes sense, and hence the "rule of 5" (if you don't want it implicitly deleted). Thanks.
- microtherion 8y agoLuckily, in modern C++, defining the copy constructor in this case (where you just want to make the default available) is as simple as: A::A(const A &other) = default;
- 8y ago
- ape4 8y agoWow that was a good post. I have read other Modern C++ things are they were so dense. If all the sections were in a little book, I'd buy it.
- saagarjha 8y ago> 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.
- glandium 8y agoauto 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).
- olooney 8y agoI 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.
- glandium 8y agoWell, 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.
- shaklee3 8y agoDoes anyone know what this means? "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.
- shaklee3 8y agoEdit: it looks like that comment on the blog is incorrect. You only incur overhead from copying or going out of scope.
- Too 8y agoThat 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.