- This is Surprises in Object Lifetime. And there's still people straggling in, I know that'll happen
for the next few minutes. Now, um, Andrei Alexandrescu is in this room and he was just telling me that his goal is to be loud enough to get our room to complain about the noise. And then I was told that
we should make it our goal to get Andrei's room to
complain about the noise. But, if you ever been to my talks, I'm not great at giving jokes. When I do, they tend to fall flat. But maybe we can come up with something to cheer at some point. Maybe if we hear them getting
loud, we can figure it out. All right. So, my name is Jason Turner. This is my intro slide,
if you'd been to my talks, you've probably seen
something like this before. Couple of things I'll just call out. I am host of C++ weekly,
which is my YouTube channel, and co-host of CppCast,
which is a podcast. I will mention, C++ weekly on Friday more. I'll ask that real quick here. Who watches my YouTube channel? Oh, it's pretty good, cool. Okay, um. Yes, I am independent and available for training or contracting if you're interested in having me come as your trainer to your company that, you can usually be worked out. Yes, move to the front. There are people in the back, if you know who I am and
have seen my talks before, they are highly interactive. Gasper's getting in the front, good job. We will be doing things in
here hopefully that are fun. And also this is approximately
what training looks like, so you have an idea of what to expect if you do hire me to come to your company. So, I have a couple of upcoming
things I want to mention. This is good as people
are straggling in still. CppCon post conference training, it is not too late to sign up
for that if you're interested. It's two days, Saturday
and Sunday, right here. Well not right here, it's upstairs. I will be giving a one
day training at C++ on C, if you're not familiar with it. That's conference that's
gonna be in Southern England for the first time in February in 2019. Now, this is a hypothetical. We are working on the
concept for this of a three day training thing that
I'm considering doing with Matt Godbolt and Charley
Bay in the Denver area in the summer. That should be interesting
because we have very different perspectives on things. So, you can hear us argue
about things if you'd like to. Okay, so, about Surprises
In Object Lifetime, I have taught this one day course called, Understanding Object
Lifetime about 12 times now. Well-defined object lifetime, and our construction,
destruction cycle in C++ is one of the key features of C++. We want to be able to well reason about the lifetime of things. And we're one of just the
few languages where we can reason about the lifetime of objects. Sometimes there are surprises however. Sometimes they are for the
better, sometimes for the worse. So, let's start with what is an object? This is a key definition if we're gonna talk about object lifetime. And I am cheating the
lights at the moment, so I'm off-center. They're not blinding me as much
and I can see you some more. Okay, how is this thing, this
struct that contains an int, different from an int? Yell stuff out. This is how it works in my talks. It has a label? Somebody said. So there's some sort of type. - [Man] It has a different name. - It has a different name. That's, that's, that's okay, yes. Gasper says it has a different name. So, if we were to look at
them from this perspective, they have the same size, using uniform initialization syntax. They can be initialized the same way. We can refer to them both as
a reference to an integer, I say don't do this in
real code on line six. Please don't do this in real code, but it is technically legal. We can reinterpret_cast a struct
is the first element of it. We can assign them the same way and access them the same way. They look pretty similar. So, if we look at some of our type traits, they are both constructable,
trivially destructible, trivially copyable, and more to the point, they are both objects. So, what is an object? The standard says, an object type is a
possibly const volatile qualified type that is not a function, not a reference, and not void. Pretty much everything is an object. Okay, so, Object Lifetime. Lifetime begins when storage
with the proper alignment and size for a type is obtained, and if the object has
non-vacuous initialization, its initialization is complete. At some point, I have this slide in a
couple of different talks. At some point I should
remove the bit about union, because we don't care about
that in this exact moment. Although adding union to our surprises could have been an
interesting thing to do, I did not do it in this talk. So, if it has non-vacuous initialization, once the initialization is complete. It's lifetime ends when if it is a class with
a non-trivial destructor, the destructor call starts, or the storage in which the
object as occupied is released or reused, okay? Any questions? We're gonna do examples and interaction and stuff in a minute, yeah? - [Man] Do you have a definition for non-vacuous constructor? - Let's say non-vacuous initialization, let's say non-trivial initialization. Yes?
- [Man] Why do they not use that more familiar defined term? - Why did they not use
the more familiar term? I honestly do not know. And if there's someone who does know why vacuous is used here? I don't know. But that's a great question. Okay, So, I have my simple utility here, that I've got the struct S, it has something that prints in each of the special member functions, and then I've got main, I am creating an object
of type S on line 12, I am creating a scope, I'm creating an another
object called s2 on line 15. Alright, what does this code print if I were to execute it? Or probably no one's gonna yell it out. I will say, what is printed on line 12? (audience mumbling) Line three, S. So, it's S with the parens, okay. What is printed on line 14? - [Man] Nothing.
- Nothing, nothing, yes. Not everything prints on main. On line 15, what is printed? S, const S ref, so the copy constructor
is called on line 15. Now, I have this kinda
annotation I personally use, you'll catch on in a second here. What's printed on line 16? (audience mumbling) The destructor. Yes, the destructor of
s2 is called on line 16, then was how I just called on line 17. (audience mumbling) The destructor of S. So, we end up with this. Now, just for the record, if we needed to at any moment in our talk we can flip over the Compiler Explorer and actually test these things. So, if we have any questions at all, stop me, we can prove though
that this what is prints. S, then the copy-constructor,
destructor, destructor. Good? Okay. Okay, now what is printed? What's printed on line 12? Constructor of S. Okay, we already said line 14
doesn't print anything, that's no change there. What's printed in line 15? (audience mumbling) Nothing, nothing is printed. This is perfectly safe. I haven't done any kind
of implementation defined, undefined thing, or nothing, nothing is printed. What's that? (audience mumbling) It's a reference. It's a reference, there's
no copy to be made. Then, line 16, nothing. Line 17, (audience mumbling) destructor, okay. So, that's what it looks like. So, remember that reference
types are not object types. They don't, we don't talk about, they don't affect our lifetime, mostly. We will show something where they do. Okay, alright, now we're gonna get into this. So, what is printed in this code? I have this function called get_data, it is returning this constant ref and then I'm printing it on line 9. What'd you say, 42? (audience mumbling) That's as good a guess as any. Most likely five, okay. (audience mumbling) Alright, so, we are returning a reference to local. It's garbage, this is unknown,
this is undefined behavior. What warning do we get from the compiler? (audience mumbling) Returning a reference to local, sorry, it's, for some reason it's easier for me to focus over here. If I am ignoring you, someone
start doing jumping jacks or something, burpies, I don't know, something that I'll notice, and plus it's good because
you're sitting all week, basically anyhow. Alright, so, yes, we might get a reference
to stack memory return, reference to local return,
something like that. Are we surprised yet? The title of this talk is
Surprises in Object Lifetime, hopefully, we get to some
surprises in a minute. Okay, now, what is printed? Now I'm returning a
reference_wrapper not a reference. It is still unknown, yes. We are still returning a
reference to a local object. So, this reference_wrapper is implicitly taking a reference to i, the local value, and returning it. The question is, now what warning do we
get from the compiler? (audience mumbling) Probably nothing, unless it's magic. (audience mumbling) What's that, I'm sorry. (audience mumbling) Oh, oh, you're saying with like the stuff that Herb is
working on right now, yes, no. I'm just talking about gcc
and clang at the moment. Okay, um, so, I'm compiling this list
here, my default's clang, I've got w_all, w everything, w_shadow, and Pitantic. No warnings are printed, we can see that in the
lower right hand corner. Gcc makes this fun, because if I change it in gcc. Sorry, wrong gcc. We get this warning. Warning "i" is used
uninitialized in this function. (audience mumbling)
Is i initialized in this function? I'm pretty sure I is initialized. This has become like a rule for me now, if I see a warning that makes
literally no sense at all, I've decided I'm probably invoking some type of undefined behavior. Yeah? (audience mumbling) Um, the only thing, so the question was, you said, it's talking not
about it's initialization but it's actually use inside of main. Yes, but the only place
that we as humans see an i is in that function. And it has been initialized there. That is why I take issue with this. What's that? Would it go with, I used no end line. I don't know. I can turn off all optimizations. No longer get the warning, yeah. So, the compiler doesn't
have enough information to see that here, yes. Okay, alright. So, simple standard library wrappers around references confuse our analysis. That's our first surprise! Opps, sorry. Let's talk about strings. What is printed from main? (audience mumbling) Hello World, does anyone question that Hello World is printed? (audience mumbling) What's that? Global data, okay. Hello World is printed, why is it allowed? The standards specifically says
evaluating a string literal results in string literal object with static storage duration, it's effectively a global, as you said. Initialized from the characters specified. Static objects are valid for
the entirety of the program. This is fine, this is totally allowed. Okay, we're now returning a string view. What is printed? Still Hello World. Okay, because string view is
basically a pair of pointers, to the beginning and end of the string. And we're not surprised right? Alright, now what is printed for main? (audience mumbling) 42, unknown. What's that? It might not get to the point of printing. But, most likely, the
answer is Hello World says someone in, who
shall remain nameless, in the front row. (audience mumbling) So, with our magic of Compiler Explorer here, we can execute this. Um, Yeah, that's, that's printing nothing, it says that returned zero, it says it successfully executed, but it didn't print any data. And we can (audience mumbling) that is gcc, turn off the optimizer, it doesn't, it doesn't
change anything really. Um, Yeah, something like that, that's an interesting unicode character that it decided to pick. Okay, so this is unknown, why? I think most of you already got this. Since the string view is pointing to the beginning and end of the string, the string object that
actually stored this data was popped from the stack
and our local string has gone out of scope. We already saw it in
the Compiler Explorer, what warnings did we get from this code? Not a single thing. Okay. What does this code print? I'm getting confident Hello World answers. (audience mumbling) I'm getting a few undefined. Why, who wants to describe why undefined? Someone, somebody, you wanna go with why, okay, why? (audience mumbling) Yeah, you raised you hand, right? (audience mumbling) And it's a, yes. (audience mumbling) Right so it's not very much of
a change from stood string because this is a local array called S that is initialized with the global data that character literal Hello World and then we are returning
a string view into that. So, the local array decays to a pointer, which initializes string view and we get no warnings. That's fun. Any questions? So, surprise! This is how I phrased it, strings live longer than
you think they will, except when they don't. I have actually, I will
take in aside right here, many of you raised you hands saying you watch my YouTube channel. I did say, explicitly in a recent episode that this is the one
feature I'd remove to C++. Remove from C++. Implicit conversion from array
to a pointer has literally no use in the language what so ever. We could require a static_cast
when it is necessary. You might say backwards
compatibility is seed, but you can throw the static_cast in there if you really need to. So, that's the feature I'd remove. Similar idea here, we are now initializing a vector and we want to ask what is printed here, when we push back an object of type S, with this constructor? So, you're saying, you're saying the S int constructor and then the copy constructor (audience mumbling) and then destructor (audience mumbling) Okay, how many people say the
copy constructor is called? You have to have more
confidence than this, raise your hands higher. Okay, who says the move
constructor is called? Okay. Alright, so, we'll the votes win, so move constructor is what it's called. And that is because there
is an overload for push_back for the move constructor. How many destructors do we see? Okay, who says, how many say one? How many say two? How many say three? Okay, I think two won also. So, we'll go with two. So, yes, we have the S int constructor, the move constructor,
destructor, and destructor. So we remember that for non-trivial type, the destructor of the moved-from object must still be called,
is the takeaway here. Surprise! Moved-from objects still
have to be destroyed. So, this is a surprise for
maybe a quarter of you. Yes, and it's often non-trivial
often inlining the destructor causes code bloat
because it has to decide, for example, with pointer, if there is
some resources that need to, excuse me, for example, if stood
string, it must decide if there are some resources
that need to be freed or whatever. Okay, I have switched this is emplace_back. (audience mumbling) You say it's the same. (audience mumbling) Yes, it is exactly the same. For exactly the same reasons you said. Same thing as push_back, we have been used
emplace_back improperly here. The point emplace_back
is that we are trying to call the constructor of the object. And so, I will build on that. Now I'm using emplace_back this way. So, now what do we see? What's the first thing we got printed? (audience mumbling) S int and then what's
the next thing printed? (audience mumbling) Constructor of S. That's it. This is the proper way
to use emplace_back. Any questions? I just see a couple of
faces that look confused, but, okay. If you're not going to ask questions, it's your own fault. So, this is the correct
way to use emplace_back, if we're gonna use it, we're calling a constructor,
is basically what we're doing. Worth pointing out, this line here, we can call it with zero periameters. So, we're calling the default constructor. So, even without a named object we have to think about object lifetime. Now, I do think that
understanding object lifetime is probably the most important part of understanding how to write good clean C++ that is efficient. Alright, what is printed here? Yes, what is printed? What is printed on line 12? S (, alright. What's printed on line 13? Hello World. What's printed on line 14? Okay, does it bother anyone that this is a const reference on line 12? Because it is a reference to a temporary. Yeah. (audience mumbling) Oh wait, sorry. Yes, Leslie? No, if you don't have
const it won't compile. You can not take a non-const reference to a temporary unless you are compiling
in a version of MSVC that is in permissive mode. (audience mumbling) Specifically. Yes. So, S, Hello World, ~ S. So, this is our surprise here. Complex rules allow for lifetime
extension of temporaries that are assigned to references. If you want to look this
up in the standards, it's in class.temporary. It is a lot of confusing things to read. Yes. Oh, uh, (audience mumbling) we will. Okay, so the question was would there might actually
be two different S objects, in this case. I will say, hypothetically, before C++17, yes, but we'll dig into
this in a little bit. (audience mumbling) Yes, yes. Actually, no wait, I
have to get back to that. Do our value references
also expand a lifetime. No, but universal ones do, says Gasper. Or forwarding references if you will. (laughs) Okay, so for the sake of this slide, since someone asked and I had not actually spent
time to think about it. Let's run through this real quick. The question was might there actually be more than one S object created. And I will say, by looking
at it more closely, definitely no. Because we are value initializing, excuse me, we are
initializing on line eight, the return value S. Directly initializing the return value. We're not returning any
local object at all. And then we have a const reference to the returned thing on line 12. Under no argument, can you say that there is
more than one object S here, I believe, except, Ben's
gonna disagree with me. (audience mumbling) Oh, it's unrelated to that. (audience mumbling) Okay, we will not worry
about that right now. (audience laughing) That's, I repeated the
question for the YouTube video. (audience laughing) Okay, so, complex rules object lifetime. Alright, I am now initializing a const
int reference on line two with a temporary, effectively, one, on line six, and putting that in a const reference. What values are returned from main here? (audience mumbling) Okay, lets do it this way, who says one, the two questions, the two
options are going to be one and undefined. Who says one? Okay, who says undefined? Ou, it's like 50/50. Okay, since there was no consensus, we have to go to the standard. It says one is returned from main, lifetime extension rules apply recursively to member initializers. I was researching the
answer to the last slide when I came upon this
example in the standard, and I thought, I have to put this in my talk also. (audience laughing) The question was what and the answer is go read class.temporary. I think it took me four
times of reading it. You read the standard
more often than I do, you probably get it faster, but, yes. Okay. (audience mumbling) I'm sorry, what that a question. (audience mumbling) Only for what kind of initializers? Is it only for aggregate initializers? Um, no, I don't think it has
to be a member initializer, I think I could have
actually done a constructor that took an initializer with a (mumbles) reference into this. I'm almost positive, yes. I don't think it has
to be, yeah, aggregate. Alright, how many dynamic allocations
are in this code? We got a vector of stood strings. Don't worry about the
version of the standard, actually, any version of the
standard that'll compile this. Okay, we have a couple of
guesses being yelled out. How many people say one? How many people say two? Nobody says two, how many people say three? Okay, somebody who said
one, make an argument. Who said one, raise your hands again. You. (audience mumbling) Small string optimization. This is not something
guaranteed by the standard, but if you want to have a standard library that's able to compete, you
have small string optimization. So, almost certainly one, we will have our a and b, we'll fall into small string optimization, we're doing one allocation for the vector. So, stood string is highly optimized, do not underestimate it. For real, if you saw the talk
I gave at C++ Anal this year, you will see that stood string messed with a lot of my benchmarks. Alright, now how many dyncamic
allocations do we have? (audience mumbling) (audience laughing) The comment was it's not long enough. Let's say with, how many people says there's
one dynamic allocation still? No one. Two? Three? Four? One person half heartedly
raised his hand for four. Okay, you want to change your vote? Okay, wait, how many says there's five? Exactly one, oh, oh oh, okay, alright, alright, I see who
else is raising their hands in the back here, um, okay. You said five, as far as people
who can actually interact with me, why five? (audience mumbling) Yes. Okay, so basically, initializer_list can't be moved from is what your effective answer was. We have an initializer, well, the answer is five. Why, I'll back it down. Because the compiler has done this for us, it has created a underlying array of string objects so those must be initialized, that is one and two, and then we must allocate
the space for the vector, which is three, and then we must copy those
strings into the vector, which is four and five. Like that. So, five allocations here. Who was surprised by that one? Everyone, except for those three people. Yes. (audience mumbling) why is it initializer_list, oh, oh, so, if you look at the this is where I'm making
the cameraman's job harder, but, I'm sorry. If you look at the, the constructor that takes an
initializer_list for vector, it is an initializer_list of string, therefore, the underlying
array that is created for the initializer_list object, is itself, an array of string. That is what the standard
requires it to be. That is why it is not an
array of const (mumbles). We will have, I think,
more satisfying examples for you in just a moment. (audience mumbling) Oh, an array of const strings, and not a, because it's not movable. And if you look at the the accessors on initializer_list it only has constant accessors anyhow. So, it's irrelevant really, We, a lot would have to change. And if I did, well, I did an hour and a half long rant at C++ (mumbles) about that. Called an initializer_list are broken. So, I'm not going to go into it right now, but, in much more. Um, (audience mumbling) Can it be optimized? Not in any way that I'm aware of. No, Hepalison with claying
might be able to optimize it but I've never seen claying
alide two Heap operations. I've only seen it alide one. Yes? (audience mumbling) Let's keep going. Oh, if you had to do it at runtime? (audience mumbling) Okay, if it were const data and you can't make the, well, do you know how many objects it is? IF you always know the number of string, you can do better. (audience mumbling) Yes? You might get only four. Uh, of the two strings? They both have, no, you can't merge the two of them, they have to be two diff-- (audience mumbling) I don't believe any
compiler would do that, because each underlying string object has to have a distinct
pointer to it's data that it can manage separately. (audience mumbling) And (mumbles) because, oh, okay, because of the as if rule it's hypothetically possible that some of the
(mumbles) could be merged. Okay, (audience mumbling) yeah. (audience laughing) He says I have seen things. Okay, initializer_list and vocations
create hidden const arrays. That's our surprise here. Um, how many dynamic allocations
does this code have? (audience mumbling) Okay, how many, who says zero? Okay, who says one? Two? Three? Does anyone wanna go
as high as five today? (audience laughing) Can I get six? I have to go into like bidding mode here. Okay. It's zero, it is zero. Because we're using C++17's
(mumbles) type deduction, it's going to deduce
this as a const char * of two elements, no allocations. So, yes, if you know what the strings are you can do perfect. Okay. Um, is this okay? Yes, okay. We know that our character
literals are valid for the life of the program. Now how many dynamic
allocations do we have? (laughs) One, who says one? Two? Uh, pretty good number of people say two. Three? Four? Hand full of people are saying four. Okay, you're saying four, alright. Yes, I overheard the answer already, it is two because this is
not an initializer_list. What is the difference? Well it is, okay, it is an initializer space list if you're looking this up in the standard. It is not an object of
type initializer_list. You have to keep those separate when we're talking about initializer_list. I don't have the vocabulary to keep them separate without
saying all of those words. (audience mumbling) Oh, I'm missing a pair of curly braces, okay. Clang user. (audience laughing) You know there are situations with clang where it says extra braces suggested and you can put like 75 and it still giving you that warning. Its a little, (audience laughing) um, yes. So, this is why. Standard array has no constructor. We are directly initializing
the data of the array. Now, interestingly, you notice this _M_elems, I don't know if it's lib C++ or lib stood C++ that literally uses this name. But, I did look it up. I was reading through the source of array. As one does. And, um, okay, so we, mere mortals are not allowed to create an identifier with this name. This is reserved for the standard, an _ followed by a capital letter. And therefore, we are also not allowed to rely on the name of this thing. It is undefined behavior
if we access something with this name as well. So, they are, they, the people who implement
the standard library, they're allowed to do this. We're not allowed to do this,
they're allowed to do this and then they can punish us if
we try to access this thing. They're allowed to. So, this is, anyhow, we're directly
initializing the array. Yes, (mumbles). (audience mumbling) Yes. (audience mumbling) Yes. (audience mumbling) Is there a reason why it wasn't characters of array length? Um, if you have multiple, I, I don't know the exact wording, I don't know the exact
place in the standard, but I've from my experience if it were one of these, it would be a character array, plausibly, with two of them it has
to deduce the common type, which is gonna be char const star star. That's, I'm almost positive
that's what's going on. Because you see the same kind of behavior if you've got, a turn array that's returning
two different strings of the same length and
that kinda thing too. Yes. (audience mumbling) - Oh, you're saying, ohh. Leslie said it would decay to a pointer because it's, template argument by value. I'm not, yeah. I think that's right, you actually have to
specialize the pinpoint based on the array. Alright, anyhow, that's a side, okay. So this is literally the most
efficient thing possible. This is my surprise, this is how I wanted to phrase this. Standard array has zero
constructors for efficiency! Just let that sink in. If you've seen any of the talks, from Neiko, about like an
initialization of trivial classes, and he goes through
great length to explain how you need all of the
different constructors to get things as efficient at possible. I'm not saying you go back to your job and start writing all of your classes with all of your members public and don't have any constructors. I'm just saying, this. (audience laughing) Alright. On to something different. Where ever my water went. What do we have? I think by now we have the rules. A new slide comes up, you tell me what happens. Okay, so we have get_s, which is
create an object of type S. Which is then immediately calling an object of type get_data and then we're printing or excuse me calling the
function called get_data, which is returning a vector
events by const reference. So, what's printed? (audience mumbling) One. (audience mumbling) Undefined, unknown. Alright, we can break this down, it is unknown because if we have a range four loop, this is literally what the standard says that the compiler is doing. (audience mumbling) An auto ref ref-- (audience mumbling) Yes. Auto ref ref would extend the lifetime of the thing returned by get_s. Not the thing returned
by get_data from get_s. It is not recursive
object lifetime extension. It is unknown because we
have a dangling reference to the object of type_s. Oh look, I even put dangling reference on the slide. It's like I knew what was coming up. What warnings do we get from the compiler? (audience mumbling) Sorry, I get ahead of myself. No, um, I'm pretty sure we're at, yeah, no warnings with clang. And no warnings with gcc. Those are the compilers
I have at my disposal at the moment. Yes. (audience mumbling) Oh. That's (mumbles) doesn't warn, is that one of the ne-- (audience mumbling) Herb's new extension, okay. (audience mumbling) Well, it's interesting that it's not correctly diagnosing this and I will explain why on the next slide. C++20's for-init. This was added in C++20
specifically for this exact kind of case, which I find is interesting that the lifetime warnings
aren't catching it then. But, it's probably, it's difficult. These are difficult problems to catch a static analysis time. At least I have to
assume they're difficult because people aren't doing them and I'd never written a static analyzer. Okay. So, C++20 lets us do this. Now, just for the record if you'd try this in
your pre C++20 compiler, you'll probably get some weird warning about how your missing the last
third part of your four loop because now we're getting
into a syntax that looks like as far as the number
of semicolons required and stuff, it looks pretty much like
it's a regular four loop. But we can see here that the compiler now generates code like this for us. It has a const auto s. We can see that line was
just copied in from line 12 to underline 13. And then it is using it's auto ref ref on the range object. Any questions? (audience mumbling) Yeah, go ahead, sorry. (audience mumbling) That was recursive reference
on a initialization not on return values. That is the difference as far
as the standard is concerned. Wait, there was. Yes. (audience mumbling) Um, well if I had done auto ref s, I would have recreated
the exact same problem. I would have created, oh no, it would be const auto. If I had done const auto ref, that would have been allowed here. But it would have been, exactly the same thing. We still would have had exactly one object of type_s created, it's just whether or not you use the const reference
lifetime extension rules or you used return value optimization. Yes. (audience mumbling) Yes. No, no, wait, just for the record. I did not suggest having no constructors. (audience laughing) (audience mumbling) Yes, if I had not had a
function called get_data here, it would have worked fine. That is correct. It actually took me
awhile to carefully craft these slides to make my point. That's, that's like a little secret. People spend a lot of time trying to get the exact example that they
want you to comment on. Alright. So, I ranged four loops, create these hidden variables for you that have their own questions of lifetime that we have to be aware of. Alright, time. I am moving too slowly. Okay. What warning might be get from this code? (audience mumbling) Shadow. Shadow warning. We can break it down, and ill start to move a little bit faster. X shadows a previous declaration of x. So, this is the code and
we will expand it out one block at a time. So, this, auto const
auto x equals get_val, it says if we had done this,
we created a new scope. I know the first time I presented this people missed the scope that
was created on line five. That is not the opening curly for main, that is a new explicit scope. So there's a new scope
where that x is created. And then we break it down further, we've got our inner x, so
that's the one that shadows. So our surprise here if-init
statements are visible for the else blocks as well. I find teaching this that some people just don't expect that. It's often the first question people ask about C++17 if-init stuff. Okay. This is similar to these things. What is printed here? (audience mumbling) S ( ~ s. Now, what is printed? I've got (audience mumbling) s ( ~ s. Okay, who says same thing? Who says something more? Alright, everyone who voted said same thing. So, I like to point out, this is required in C++17 but every compiler I've
gone back to 1995 does this. It's it's been a thing for a very long time. (audience mumbling) The mechanism changed
depending on your ABI. (audience mumbling) Yes. As of the, what itanieum IBA if I'm being technically correct. (audience mumbling) Yes. Well, (audience mumbling) okay, well, you know what I'm almost out of time, Gasper's arguing that
it's not an optimization because it's required. I would like to point out the mechanism because it actually works on most ABIs. Is the object, little s, on line 16, it's memory is allocated right here. A pointer to it is passed forward, into get_other_s, which is
passed forward into get_s, which is initialized. That object never moved. From a compiler standpoint, there was nothing to optimize. It was always right there. Yes, Ben. (audience mumbling) Yes, prior to 17, you still
had to have the copy instructor even though it was never going to call it. Yes, which is a little annoying. But, now, you don't have to. (audience mumbling) You're right, so yes. It's not longer an optimization, it's not under the (mumbles) rule. Alright. RVO is super awesome,
or illision, whatever. Same, (audience mumbling) dag gummit. Make me change my slides on the fly because if I did this, what changes? (audience mumbling) Yes. The answer was now it is NRVO. Named returned value optimization, I believe I have that correct. It is, it has another letter. We are going to get the exact
same output from the compiler. It's good at these things. So, it's easy and I don't like Copyalison, I don't like the frays, Copyalsion. I also don't like return
value optimizations since everyone's done it for 30 years. We shall move on. Okay. Where'd my controller go? Alright. This is a little bit more complicated now. I've got this thing called a holder. I am constructing a holder
with my function on line 15 called get_Holder. It is initializing the return value and then I'm calling dot_s. So, I'm initializing my local variable s on line 16, with this thing that was
returned from a temporary. And then I'm returning
that from the function and then on line 21 I'm initializing this. Alright, what's printed here? (audience mumbling) S (. Alright, what's the first
thing that's printed? S (, let's get that out. (mumbles) what's the next thing that's printed? (audience mumbling) what's that? (audience mumbling) Move, did you say move constructor? Copy constructor. Okay. Who says copy constructor? Who says move constructor? Okay, no one says move constructor. It is move constructor. It is the move constructor because the thing returned from get_Holder is an R value, therefore, it is a perfect candidate for
calling the move constructor. That's the simple way of wording this. So, on line 21, nothing is printed, because as we said, that
object little s on line 21, is actually like it's
memory was allocated here and it was passed forward, and the things. Okay, so we have an s, a move constructor, and a destructor and destructor. So, moves happen
automatically with r-values. Don't try to help the compiler. I've changed this to a structured binding. Now what's printed? Someone says the same thing? Who says move constructor? Now? Oh, now no one wants to raise their hands for move constructor. Who says copy constructor? Okay, the rest of you have just given up. Okay, you're saying copy constructor. It is a copy constructor because the compiler says that this code was magically created for us. This object s, that we
thought was a local object is actually a reference to the temporary that was returned by
the get_Holder function. (audience mumbling) No, (audience mumbling) it is not a referenced, it is a it is, yes, an alias. It is, no sorry. Which one are we referring to? (audience mumbling) No, no that's definitely a reference. Line 17 is definitely a reference. That is, you can look up the wording of structured bindings and see
these kinds of examples. (audience mumbling) I have had conservations with people involved in the standard that say we can't tear apart objects so, no, currently, the as if
thing can't do things here. But, that is outside of
my depth at the moment. Okay, so our structured
bindings mess with our ability to get our moves. So, in this code, is the destructor called? We are creating an object on line 12. Do we have any reason to believe the destructor of s is not called? It is not trivially destructible. It has a destructor right there that puts. So, we expect to see ~ s printed? (audience mumbling) Okay. Now, I have put a throw
statement in my constructor. Do we see the constructor called? (audience mumbling) Okay, so I'm getting yes, no,
no, yes, yes, no, no, yes. Who says yes it is. I hope I didn't need that. I don't know what that is. Okay. Who says yes the destructor is called? Okay, who says no it's not called. The nos, the nos have more hands, so therefore it's the correct answer. (audience laughing) This constructor call did not complete. The object's lifetime has not begun because it's constructor
has not completed. (audience mumbling) (mumbles) Shhh, (clearing throat) okay. I have added a call to the delegating, I've added a delegating constructor call. Is the destructor called now? (audience mumbling) okay, so I get some emphatic
yes's from the front row. Yes, it has been called, why? (audience mumbling) Yes, effectively when the
first destructor exits is when the object's lifetime has begun. So, when this default
constructor on line seven is completed, the
object's lifetime has been considered to be have begun, therefore, it's destructor will be called. Now, if you really want to have fun when you want to play with this later, have a bunch of random sub-objects that throw destructors at
different points in construction and play with what gets printed and learn all kinds of things about how much work the
compiler actually has to do to keep track of which objects properly had their lifetime begin so that it knows which
destructors to call. And then, thank your compiler developer for getting it all correct, probably. (audience laughing) As far as I know,
there's still an open bug for something about this
in gcc that's like old and I forget the details though. Alright, so once the delegating
constructor completes, the object lifetime has begun. So, this is from a, I should
have had a link to it, a rather popular post by Howard Hnnant on stack overflow. And this is the kind
of example he made here is that we can do interesting things to make sure that our sub-objects lifetimes are managed correctly. So, if one of them were
to throw an exception. Like if the line eight
were to throw an exception, we know that the object that
was allocated on line seven would be properly cleaned up. This is valid defined behavior. However, I put it in really big letters. I hope you can read it. Don't do this at home, use unique_ptr. This is, this is, violates all kinds of like never call new, never call delete, don't use raw pointers. Don't manage the lifetime
of more than one object. Yes, all kinds of best
practices are violated here. So, an object lifetime has begun after any deconstructor is completed. This is my bold title conclusion slide. Anywhere where the specs say the compiler transformed the code for you, there might be a surprise. If you get bored and you're
reading through the standard you will see some of these things. Avoiding these issues. We need to think about lifetime. We have our delegating constructor, it's just a waste of code, right. We can just return the first element if that's really what we wanted to do. So, don't name temporaries. This helps us think about
object lifetime correctly. If we don't have a name, we don't have to worry
about it, pretty much. This is a thought. I haven't tested this in real life yet. Consider requiring that all
your structured bindings actually be by reference. Because this then communicates that you are actually
dealing with references here. You're going to get the exact same results but now you have this note
effectively in your code. I don't know. Who likes that idea? A hand full of people. Who does not like that idea? (audience mumbling) (audience laughing) I will not repeat that comment. (laughs) Using our tools. Warn all the things, thanks Ben. Wshadow catches some of these things. Clang-tidy core guideline
checkers would catch many of these things. The good warnings and
our guideline checkers check all, any implicit
conversion from an array to a pointer is caught. I love that. Sanitizers, every single
one of these examples is trivially caught by a sanitizer. Are you using sanitizers on your code? (audience mumbling) Yes? If you're not, when you leave this session go implement sanitizers in your continuous integration environment, which you're all using, right? Yeah, everyone has a CI of some sort? Alright. So, be careful with initializer_list. Effectively, it was
really only intended for trivial literal types, keep that in mind. Oh, oh yeah, I can't skip this. Constexpr all the things. This is invalid d reference,
we know this right? I have like two minutes left. So, bam! What does this code do? (audience mumbling) It fails to compile, why? (audience mumbling) Because it doesn't allow
undefined behavior, it says it right there on the slide. Good job. Okay, taking this example,
where we have this where C++20 is four initializer_list, four initializer is supposed
to help save us here. This this example caught by a would be caught by a sanitizer. If we make the context perversion of it we're in the exact same place. Compiler will not compile it because it see undefined behavior. I can actually, execute it, or compile it real quick. We get a constexpr variable is not initialized by constexpr. And I love the warnings that you now get in a constexpr context that you can't get from basically any other tool. Read of object outside it's
lifetime is not allowed. What! (audience mumbling) An a constant expression. (audience laughing) But it's not allowed in general. If you can build your code in such a way that you can do constexpr testing you're going to catch
many more potential bugs. Similarly, string view
is constexpr enabled. We can do this, this version
now refuses to compile. So, yes, I have 42 seconds left. Yes! I'll leave that up. Any questions? Quick escape! Um, any questions? Yes? (audience mumbling) Do I know the rationale for the, if the init variable is
visible in the l statement? Once you actually get used
to using the if-inits, it really is the only
thing that makes sense. Because you're going
to initialize the thing and then you might want
to take two different two different actions on that object depending on whether or not it was some other thing
evaluated (mumbles). It really is the only
thing that makes sense. But it's, you're not necessarily thinking about it when you go to use it. Anything else? Okay, so, a question way, you might have to run up to
the microphone or something because, there's one right here. Run faster! You have negative minutes left. Yes. - [Audience Member] I
have a question about, well I think the NRVO portion got skipped quite a bit because the
compiler can't possibly get all the cases of NRVO unless if you know that there is a unique source. = Right,
- [Audience Member] So, - Yes, well if you think about that that pointer that I said that object that's created here and then a pointer is
passed forward to it, it clearly, that pointer
can only be in one place. It must only be used to
initialize one thing. And you're right, if you have multiple well, if you have multiple, - [Audience Member] Return statements. - Return statements, with named objects, then yes. It can't do the return value
optimization the same way. One of them will get it and the other one will get
a copy or whatever, or move. Yeah.
- [Audience Member] Sure, I mean, I just think if you're depending on NRVO, your code's gonna get very brittle. - Oh, yes, if you're depending, oh okay. Well, I don't need to repeat it because you're on the microphone. I agree which is why I also am pretty strong on this don't name temporaries if you don't have a reason to. I try really hard to not name the things that I'm returning from
functions, personally. And it makes the code much more modular and makes my functions shorter, and I'm happier in general. Anything else? I know it's time to go, I don't want to keep anyone
over who needs to like, do whatever, eat dinner, or something. Alright, thank you. (clapping)