
Modern C++ for C Programmers: part 5 - ahubert
https://ds9a.nl/articles/posts/cpp-5/
======
jwilk
Previous 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
Thanks! I missed this the first time through

------
stephencanon
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.

~~~
ajross
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).

~~~
stephencanon
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.

~~~
BeeOnRope
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.

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
Ah, yes, that's a nice example.

------
rrmm
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.

~~~
merlincorey
Single-file header libraries are actually all the rage in C++ these days.
Here's[1] a list even!

[1]
[https://github.com/nothings/single_file_libs](https://github.com/nothings/single_file_libs)

~~~
std_throwaway
This is what you get if you're missing a proper package management that allows
users to easily install their libraries.

That single file is the substitute for a package that you can simply put
somewhere, include it and it works.

~~~
tom_
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.

~~~
stepik777
I.e. all the things you would get from a proper package manager if there was
one for C++.

~~~
tom_
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 :(

------
ttul
I wish I had a project that warranted C++ right now. It’s a magnificent
language.

~~~
csears
What would make a new project a good fit for C++?

~~~
andrepd
Performance critical.

~~~
std_throwaway
Also if Rust is not cutting it yet in your specific niche.

~~~
jeremiep
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.

~~~
std_throwaway
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.

~~~
jordigh
Why wouldn't D work on microcontrollers? Runtime is too big? GC too difficult
to avoid? Even in -betterC territory?

~~~
creatornator
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.

------
mlevental
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.)

~~~
swaroop
Have you tried "A Tour of C++"?
[https://www.safaribooksonline.com/library/view/a-tour-
of/978...](https://www.safaribooksonline.com/library/view/a-tour-
of/9780134998053/)

~~~
runevault
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).

------
saagarjha
> 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.

------
shaklee3
Does 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
Edit: it looks like that comment on the blog is incorrect. You only incur
overhead from copying or going out of scope.

------
Too
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.

------
glandium

        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).

~~~
olooney
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.

~~~
glandium
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.

------
ape4
Wow 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.

------
mikec3010
> 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.

~~~
datamart
The implicitly-declared copy constructor is deleted if there is a user-defined
move constructor. See:
[https://en.cppreference.com/w/cpp/language/copy_constructor#...](https://en.cppreference.com/w/cpp/language/copy_constructor#Deleted_implicitly-
declared_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.

~~~
microtherion
Luckily, 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;

