 So C++ moves semantics. This is new as of C++11, just a quick little background. C++ is an evolving language. The standard changes over time. So sometimes you'll hear people will say C++98 or 2003 or 2011, 14, 17, so on. That's as a new standard comes out. It's adding things or removing things. So anyway, as of C++11, there's a concept of moving. So here's an example where we have this function getName. It returns a string. And then we call presenter.setName with the result of getName. In old style C++, we would consider that temporary. And so this thing, the string that's being returned, it doesn't have like a variable it's really being stored into. It just kind of gets silently created and then passed along and silently destroyed, hence the temporary name. But that means that we're going to be making lots of these temporaries, constructing them and destructing them, making copies of them. That's a little bit wasteful. Especially if we know the thing is about to be destroyed anyway, then, you know, why create a new thing, destroy it? It's kind of silly. So for a string, you can imagine there's the temporary string and it points to the buffer that's actually holding the characters. And then when we call presenter.setName, maybe that's a separate variable of string. And then we copy the buffer into a new buffer. It also contains the characters Chris. That's where we kind of lose this efficiency where we now have two buffers and we're copying all the characters over. So move semantics are just this. It says, well, I know the temporary is about to be destroyed anyway. So what if I just kind of sneakily steal its buffer and then I'll set its pointer to null so that when it tries to clean up, there's nothing for it to clean up. That way it can go ahead and get destroyed and it doesn't matter. And I didn't have to make a new buffer and copy every single character over. That's all move semantics is it gives you the option to know if a thing is about to be destroyed and it's safe to steal buffers from it. So this is the old style copy constructor and copy assignment operator. And this is what we're used to, you know, the variable that's passed in is a const reference. In C++11 with this move semantics, we've added this. It's a move constructor and a move assignment operator. And there's two main differences here. One is there's a double ampersand instead of a regular ampersand. That indicates that the variable that was passed in is an rvalue reference. And that just means it's the thing about to be destroyed. The other change is it's not const. The reason it's not const is because we need to be able to update it to change its pointer to point to null. That way, you know, when it gets destroyed, everything's fine. So here we have, you know, we're creating a variable named name. We're storing a string in it. And then we're calling presenter.setName. We're trying to pass in this name, but we want to move it somehow. If we did the old pass it in, it would make a copy. And so what we can do is just sort of static cast the variable to there's that double ampersand. So we're casting it to an rvalue reference. It's kind of like we're telling the compiler, I'm not going to use this anymore. The thing's about to be destroyed. It's okay to go ahead and steal my buffer. So that's one way to do it. But that's kind of difficult to read. It's kind of a mess. So that's where standard move comes in. It's the exact same thing. All it does is cast your variable to an rvalue reference so that we know we can steal the buffer from. So here we have two versions of the set name function. The first one is the old style we're used to. It takes a const reference. The second one takes an rvalue reference. This is where we actually kind of ran into a problem. C++ as a community, we got really confused and we said we actually don't know how to write functions anymore because these move semantics make us overload like this. So this is only one parameter. Now we have to have two functions. Imagine if somebody reports a bug, I go fix the bug in one of the functions and I forget to fix the bug in the other function. That's no good. But if it was more than one parameter, if it was two parameters, I need to have four functions because it could be lvalue, lvalue, lvalue, rvalue, rvalue, rvalue, rvalue, lvalue. So you have this combinatorial explosion as more and more parameters become potentially sync parameters come potentially rvalue references. It just gets completely out of hand. And the whole C++ community is just like, what do we do? We're lost. So we've all kind of agreed on a solution that it's not perfect, but it's as close to perfect as we're going to get. We do this. And if you are used to old style C++, your brain is probably going, no, that's a code smell. You're not supposed to do that because that's going to make a copy of the string. And it's true. But it'll also make a move of the string. So essentially, when this function gets called, it has to construct a name. And there's a lot of ways it could construct a name. It could copy construct it or it can move construct it. That means this one function covers both of those last functions. So now we don't have this combinatorial explosion where you have multiple functions. You don't have to worry about that anymore. And if we move constructed it, it's no big deal. Moves are supposed to be cheap. We're just changing pointers. But if we copy constructed, well, then, yeah, we had to do one extra copy. That's worth it to not have four different functions. So that's the trade-off everybody sort of agreed upon. Which means with new style C++, we have to unlearn all our old habits and sort of learn these new habits. Okay. So here I want to just imagine a scenario. We're not supposed to write code like this at Google. But I want to explain a thing. So here we have that function. And we're taking an rvalue reference. And we want to pass it further down. So that's when we would do this move. But there's something a little weird. It was already an rvalue reference. So I kind of don't need to move it, right? Well, that's where things are another, like we have to unlearn old habits. Even though it looks like the parameter is an rvalue reference, it's not. That just means this function binds to an rvalue reference. But once we're inside the function, it's an l value reference. It's the old style reference we're used to. So that type is what it binds to, not what it is. And then we have to cast our lvalue reference back to an rvalue reference. So that probably got really confusing. So let me try to like clear this up a little bit. Let's see. If I... I have notes on this. Oh, shoot. I don't... There's the next one. I'm going to skip this. I'm going to come back. It was confusing. But hopefully the idea is that like... Oh, I remember what I was going to say. Phew. So the easy way to know if something is an lvalue or an rvalue is if it has a name, if it has a place to live in memory, if it's a variable somewhere. So you remember earlier the example where we returned something that didn't have a name, it didn't have a variable place to live in memory. So that would be an rvalue. But here it has a name. It has a variable, it has a place to live in memory. That's why it's an lvalue. Now, this is 99% true. There is a version that's 100% true, I could tell you, but it's way more complicated. So I think everybody's happier just learning like if it has a name, it's an lvalue. Even if this says it's an rvalue, it's not. That's why we had to cast it back. Okay. So I think that makes it less scary, right? I'm seeing some nods. I'll take that. All right. So like I said, in Google, we don't actually write this sort of thing. We only write the move constructor and the move assignment operator. So having a different function like this and then taking an rvalue inside it, that's kind of a no-no. So at Google, we would write it like this and same in Chrome, obviously. Okay. So that's all of move semantics. So I'm going to jump back in time now, way, way back to slide one. Question in the back. That was a really good question. The question was about when is it appropriate to use a forwarding reference? So that's very complicated. In this slide, I'm passing an rvalue reference to a string. But let's say that function is templated. So it's like template type name t and instead of taking a string, it's taking a trefref. All of a sudden, all the rules are out the window and it's something completely new because trefref binds to everything. It binds to an lvalue reference and rvalue reference, everything. So what that means is this standard move, remember, that's exactly just a static cast. It forces it to be an rvalue reference. But what if the thing that was passed to us wasn't an rvalue reference? We shouldn't really cast it because it's not about to be destroyed. That's where a forwarding reference comes in. So that says if it was an rvalue reference, go ahead and cast it back to an rvalue reference and keep forwarding that on. If it was an lvalue reference or anything else, don't cast it to an rvalue because we don't want to destroy the thing and steal its buffer and pass it on as it should have been passed on. Okay, that makes sense. Question. The question was when can the compiler convert an lvalue to an rvalue on its own? I don't think the compiler ever will. So in the original example where we just returned a thing, that was already an rvalue reference. So I don't think the compiler can promote an rvalue to an lvalue, I think. Good question. Yes? Sure. So the question was returning an rvalue reference. And if I say like return thing, and I go, you know, I could have just moved this because I know it's about to be destroyed anyway. So instead return standard move thing, right? And now the compiler says, I was going to alight that copy and I can't anymore because you told me to move. So what ends up happening is first things first, it already was an rvalue reference because it's the thing that's being returned is about to be destroyed. And so you didn't actually have to move it. But the compiler can be smart and say, here's the stack frame that called our function. And then here's the stack inside our function. Rather than put the thing that I'm returning here, and then I have to clean it up. I know it's about to go down here. So I'll just like work on the thing in there and avoid that copy in the first place. So that's even better than moving. So that's why the compiler says like, even though it was an rvalue reference, I'm going to treat it like I was going to copy it, and then I'm going to skip the copy. The style guide comment. So once like that. Yes. For me, this is like a clear intention. You say, oh, I was given, I was promised that rvalue coming in so I can move this and feel good about it, right? Now when you remove this because we can't use this, then how do I very least ensure that I'm not making a copy? Or how do I know other than a comment that I can do this? I guess comments will be answered. That's a great question. So the question was, this provides a very nice guarantee that the thing was an rvalue. And if we go to what we're forced to use, we don't get that guarantee anymore. And that's unfortunate. So I've actually, if you look at how do I say there's a mailing list of people like language lawyers at Google, and they're debating what is the correct thing. If you go back in time, you'll actually see me arguing exactly that. And then like two years later being like I've changed my mind. So I was like really bitter and pushing and then they're just like, stop, you're wrong. And then I was like, yeah, you guys were right. So here's why. If we have this, by the way, it's important that I should point this out, you can do this, but it's kind of like an exception to the rule. So the rule is don't do that. But if you have a really good reason, talk to some people get like owner's approval, okay, fine. But this is really saying, I was the one being called. I was the one who wrote the API. I don't know what the caller wants to do. I don't know if they wanted to make a copy or not. And they could have had the option either way, like even if I wrote it this way, they could have made a copy and then moved the copy in, right? But then that's putting the burden on them. And it makes the code kind of unnatural, whereas just calling standard move is a lot more natural. So the idea, as I understand it, is this, sorry, this allows the caller flexibility. They can do whatever they want with your API. And now the burden is on you to like use it correctly, I guess, but it's not locking the caller into an awkward state. Okay, so we do have a whole bunch of stuff we can go back in time and try to jump through. Is everybody satisfied with moving? All right, sweet. I'm going to have to rush these slides, but that's fine. The funny thing is, I was telling Sam, he's like, oh, I'm going to be short. You know, I don't need all this time. It's so wrong. Okay. Hi, my name is Chris. You can find me on social media as program max. Let's talk about C++ memory. C++ didn't have a memory model until 2011. Thank you for coming to my TED Talk. Subscribe, like, comment. Okay. Yeah, bop the like button. Ring the bell. All right. So I'm being a little bit unfair. It didn't need a memory model. We had this other thing. And here's why. Prior to C++ 11, C++ didn't have threads. And I know everybody's going like, yeah, I did. I had threads in my program. The operating system had threads, not C++. The standard never mentioned threads. And so instead of a proper memory model, we used something called sequence points. You may not have heard of sequence points, but you're actually probably familiar with the idea of sequence points. So imagine I have A equals 1, B equals 2, C equals 3. The compiler can go, you know, I can rearrange those and you wouldn't know. I can fiddle with memory however I want because there's no observable side effect if I fiddle with these. Because I never read the value of A, right? So if I change it to like A, C, B, it looks fine. And that gives the optimizer a lot of room to move, right? It can do whatever it wants. This is a good thing. And that's why it's like, as if it was sequential, that was the promise. And that's what these sequence points are for. But as soon as we introduced threading, if it was A, C, B, the other thread could come in and say, like, okay, A has been updated. B hasn't been updated, which means C shouldn't have been updated either. Uh-oh, it has been. What's going on? That's impossible in the code. So that's when we're like, okay, if we're going to add threads, we have to have a proper memory model. We can't use this sequence point nonsense anymore. So this is a tautology I recognize, but different hardware works differently. If we're trying to define a memory model, how do you say like, well, all memory behaves this way? We can't. And it gets really, really, really wild. So I told you the example of the, you know, A, B, C in that order, even if the code didn't get rearranged, even if we wrote A, B, then C, the CPU itself can be like, well, I'm going to retire this instruction first. And then that's being added to the buffer of writes that are pending. And then those writes can get flushed in whatever order they want. There's many, many steps where these can be reordered. So the reading thread comes in and observes something completely different than what the writing thread observed. And it gets even weirder than that. Two reading threads could observe different behavior, different orders. It's nonsense. So trying to make a memory model out of this is just bonkers hard. The way C++ did it is said, we will do what the hardware does and the bonkers hard stuff is on you, the programmer. You're welcome. So we are on slide five. And I've already talked about hardware and operating systems in a C++ talk. That's because the three are intimately linked and the rest of the slides are going to be like that. They are going to intertwine a lot. So I want to talk about memory mapping, which is a kind of unique thing to C++. I mean, it's in other low-level languages, but it's not in Java, for example. So let's imagine we have this hardware of ours. Maybe it's like GPS or a sound card or something. We could add this on-off switch to it. And that allows us to save some power, right? Save battery. That's great. How do I expose this hardware on-off switch to code? You know, a driver wants to disable this thing. How do I let the driver do that? That's where memory mapping comes in. So what we could do is say, this hardware is actually going to hijack the memory range BBB to CCC. So if you try to access that memory, you're not getting memory. You're getting the actual hardware. And then we could say, okay, well, at that memory address BBB, the first byte, maybe that is what controls the on-off switch. This is how we would write the driver code. So we have this volatile char pointer. That's just a byte, right? And we get to set it to the exact address we want. And then, you know, we have turn on, set it to true, turn off, set it to false. That's easy. Not so bad. There's two things I want to point out there. One is we got to specify the exact location and memory. That's not available in a lot of languages. The other is that volatile keyword. So what that means is the memory could change behind the scenes. If we just read that value, and then we do some other operations, and then we go to read the value again, the compiler could say, oh, I know the value hasn't changed. We didn't change it. But the hardware maybe changed it. So volatile is saying, you know, don't assume you can skip that read. Read it again. Okay. How am I doing on time? Alignment and padding. So we have an integer. The size of the integer is four. And the example I use. Natural alignments are going to be multiples of four. Zero, four, eight, yada, yada. We have a char. It's size one. Natural alignments are multiples of one. Zero, one, two. Easy. It doesn't have to be this way, but this is really common. So now I have a class, foo, and I have a char. We know that's size one. And I have an int, size four. Uh-oh. That didn't line up on the zero, four, eight, because that char kind of shifted us over one byte. So this is unaligned. We'll get back to that. So then does that mean the size of this whole class is five bytes? He's going, you're right. Okay. So if I say offset of the member a, that's zero. That means the a, the char begins right at the beginning of the class. However, offset of b, the integer is four, not one. It was supposed to be only right after the char, but instead it got bumped. So what that means is the compiler actually did this for us. We have our original char. We have three extra chars just to pad out. And that puts our integer back on a four byte alignment. So good. We're aligned. We don't even have to worry about that whole unaligned nonsense. And the size is eight. This is cool, except it's kind of uncool because, you know, what was the size of my class? I don't know. Let's ask the compiler. That's weird. I should have a little control. Okay. So let's talk about what happens if it is unaligned. So nowadays, processes are pretty darn good and it's probably not a big deal. You can probably do unaligned access and it'll be just fine in most cases, but in some cases it'll be a little bit slower. Okay. That's not that bad, but I have this one horror story that I love so much. I have to share it and I have time. There is a power PC chip where if you did an unaligned access, the chip would actually fault and say like operating system, if you want to try to recover, fine, but like we have aired beyond your coverability. But then this old version of macOS would go, you know, an unaligned read, that's not so bad. What I could do, I know the program was trying to read here and it was unaligned. So what I can do is read the chunk before and the chunk after, those are aligned. And then I can sort of shift things around and I can fix what the program was trying to do. And then I can just resume execution and everything is going to be fine. Imagine you're the programmer and you're like, why is my program slow? It's silently being slow down. You have no warning, no indication of what's going on. And then imagine you're like reading the documentation to the chip because that's how desperate you got. And you're like, oh, unaligned access would fault, but we didn't fault. So it can't be that. So this is like a nightmare. Somebody figured it out and I don't know how. Okay. So other weird padding things. Here we have class foo starts with int char char. So that looks like it should be size six, but it's eight. I'll get to that later. Don't worry. But right, like the int is aligned, the char is aligned, the char is aligned. Everything seems fine. But then this one, I have a char, then an int, then a char, that made it bigger. That's because it had to align the int. We know that much. Okay. No big deal. But that should, like, you know, this should have been size six and it was eight, and this should be char is one, but then we pad it out. So that's four, add four to that for the integer. We're at eight. Add the last char should be nine. How did we get to 12? Right? Show of hands, why, if you know why, show me your hand. Why this is eight instead of six. Okay. We have three, four, five. Six. Okay. Not that many people. So imagine I have an array. There's the first element, and then there's the second element and the third so on. That second element, if this was size six, on the second element, that int wouldn't have been aligned. And so it actually added some extra padding here so that when you have the second element following the first one, the int is still aligned. And that's the exact same reason why this thing is 12. It had to add some padding at the end so that on the next thing, it's aligned again. So that means not only do we have padding to line up this integer, we have padding at the end to line up the theoretical second element if we ever put it in an array. It gets pretty messy. Yes. That's totally correct. So to echo that on microphone, it doesn't have to be an array of the same thing. It could have just been a different type following it. You're totally correct. And different architectures, different compilers are going to do different things. On the platform I was working on, an unaligned int access is actually fine. It's not a performance penalty. So it didn't need to pad out the first int, but it did anyway. And I think it's just like, well, maybe he'll copy it to an old machine, whatever. There was a question somewhere. Shouldn't you be moving it to an old machine? Don't move it to an old machine. Question. Oh, that's a good question. I don't have an answer. I'd have to check. So the question was if foo, this class is a member of some other class, rather than padding out with empty bytes, could it just use those bytes? And I don't know. Theoretically it could, maybe. So long as things are still aligned. Question in the back. Correct. The question was on the example of the right, why can't the compiler reorder the fields to get this? The reason why is because our class is kind of like a struct in C. And that was actually used to do memory layout. And so if you start rearranging things, then it's like, well, I thought two bytes after my pointer would be this variable and it's somewhere else. And so that's why.