A completely agree with you that passing primitive data types by reference is not a great idea. It will most likely prevent compiler optimizations. However, I think its probably a good idea to pass custom types by reference, even if they are small. The reason being that their size (and internal behavior) can easily change in the future, without the programmer later realizing that these function parameters all need updated to reference types. After 20 years of programming, one thing I've learned is to treat everything I've touched as unstable and dynamic.
I heartily approve of doing the code reviews by topic. If it tells you anything, this is the very first one of your code reviews I've ever watched--specifically because it wasn't ridiculously long, and I could see in the title that it was about something I might find useful. You might get fewer views per individual video this way, but I have a feeling you'll get *way* more total views overall.
Same here. I once started like a 1h video of code review, but quickly I got lost and never finished. Instead, for this quick video I already entered knowing what was the topic, was direct to the point, easier to understand and, well, I saw to the end.
This was definitely more easily digestible and focussed than 40 minutes of code review that goes in five different directions. I smashed like so hard on this that I almost dropped my phone.
this is absolutely a better format, not only because my attention span is garbage but also it really helps to build a library of "advice" video that's easily searchable even without the context of the code review this kind of advice is quite rare in a normal beginner tutorial and StackOverflow can be quite asinine about it this way your video can reach a broader audience who just need specific advice
100% love the shorter videos. Recently I haven't been able to find the time watch longer code review videos, so breaking them down makes it much easier
Divide by multiple topics, that will help your channel on the long term as more videos will popup on RUclips search. And that will help less skilled programmers(like me) to learn specific topics.
Just look at the assembly code my friend, I bet you will get a couple of surprises, particularly with private methods. Hint: the compiler does not always honour the argument passing convention you specified
@@andyyy1094 I mean that you can write code meant to pass or return parameters by value, particularly long structs, but the compiler may decide to pass them by constant reference if such parameters are not modified inside the function and the function is contained in the same translation unit (C static function, or C++ private method). Furthermore, even if the function is not private, some compilers can go a step further and create two versions of the same function, one to be called (often inlined) using references when invoked from the same translation unit (regardless of what passing convention you specified), and another one ready for external calls. However, what is said in the video is correct and programmers should keep with good practices and never assume that the compiler will optimise everything
@@urisinger3412 What you mean by don't trust the compiler? Compilers are by far the most trustable pieces of software in existence. Can't imagine the mess if it wasn't like that
I just want to join all those that have said that it is an excellent idea to divide the code reviews into smaller videos focusing on one concept. It is super useful!!! Also, you get the opportunity to explain the same concept in multiple and different code sets which will greatly help understand better the concept, you know, by looking at it from different angles and use cases.
making short code review responses that address very specific topics would be perfect for...well, shorts! I've seen a couple channels try out short "tips & tricks" videos around code and they've been very useful!!
Great Idea go for it. That is the exact reason why I watch long code review videos. At any random time in a video, you give a specific knowledge for a specific situation that normal learning seriea doesn't even think about to teach us. For example this video enlighten me about r and l-values. Thank you for that. Learned C++ with your C++ series years ago and now I can develop custom tools for the games I make with Unreal Engine.
Splitting a long code review into more specific and shorter videos is a fantastic idea. As you said anyone could focus on particular topics that may be intersted in and understand a topic better without being overwhelmed
This video is good advice, it's basically F.16 in the cpp core guidelines: For “in” parameters, pass cheaply-copied types by value and others by reference to const. F.15 has a really good diagram that illustrates all the choices. IMO it's a good idea to keep your argument definitions 'const', even when receiving value types. Especially in larger methods it's useful to know for certain that a given argument hasn't changed. Also, don't use 'new', 'array'/'vector' would trivially give you the all-important move constructor for that buffer.
@thecherno3 OMG thank you! What did I won?! I can’t believe it! It is amaaaaaaaaazing! I hope you’re not a fake OMG! I hope it is a house! Is it a house? Or a 35 levels building in the middle of Central Park?! Or, more amazing, 15kg of twinkies!!!! I hope this is not fake OMG! WHAT IS A DM? HOW TO DM? WHAT IS THE PURPOSE OF LIFE?! WILL « TRON 3 » BE A GOOD TRON SEQUEL? SO MUCH QUESTIONS!!!!!!!
Explaining the "why" of anything is extremely valuable, at least imho. throughout all of your content that I have watched, the most enjoyable is when you explain quite trivial things but explain the reasoning at a really low level.
Oh, my dear Cherno. 1. Roughly at 1:20 you said that const reference is converted to ptr. Actually, not just a "ptr *", but "ptr * const". 2. Passing argument to a function depends on ABI. If your ABI declares that all arguments must be passed via stack - then yes, small types converted to pointer will take more memory. In old ABIs agruments was always passed via stack (see Intel x86 ABI as an example). In modern ABIs arguments mostly are passed via registers which always have same size. Only data with sizes higher than register size always passes via stack. The main difference here is number of accesses to a memory holding the value and it will be "long" if variable located in different memory page, which will have to be kept in RAM. 3. At ~4:59 you saying that "type is not trivially copyable" is an issue of passing by value. This is not the sole reason. If your type is trivially copyable, but is a big sized (too many members/fields) - this is also an issue for passing by value, because you'll eat all the stack.
With (2) if your passing by reference then the reference needs to point to a place in memory, if this is something that was previously just a local variable this is going to force it onto the stack just so that it can be passed as a reference to the called function. ABIs difffer a fair bit it's not just size that impacts things but also if the type is trivial, if the ABI splits structs (like System V), then you have some which don't have many registers for arguments like MS ABI Also 32-bit x86 is pretty much dead these days for any game you ship and not worth worrying about, everything targets 64-bit now.
@@Raspredval1337 You can break ABI only if you write the call by hands in Assembly language. ABI is the convention which is followed by compiler, not the developer.
@@malckhazarsteamcat9817 You mean the platform. The compiler just targets the platform, so if the convention used is wrong it's because the compiler targeted the platform incorrectly.
@@anon_y_mousse No, you mistook. ABI (or Application Binary Interface) is a convention declaring the order how calls should be made in Assembly. It is included in that you call a "platform", but, technically, it does not restrict you to use other orders of calls inside your code. The problem will arise only after you violate ABI used in compilation of other binary parts of your app - libraries, system calls, etc. Inside your code no one can restrict you from writing the call to your function in Assembly.
The part of the video where you said you should probably make an entire video about, I think it was around 2:15, that would be very helpful. The CPU is still very much unknown to me. And individual videos would be great especially as a playlist too.
I agree that having separate videos on specific topics is better. I'm currently struggling with my side project in C++, and I can say I'm already pretty comfortable with templates and even type traits-topics considered quite complex-even though I've never used them before. But I still often struggle with basic things like passing by value, reference, or pointer, or knowing when it's better to use shared_ptr or unique_ptr, move semantics, etc. Probably that's because I've been employed as a Python developer for years, and I deal with abstractions and polymorphism with dynamic types every day, but some basic C++ concepts are new to me and others like me.
Definitely prefer this format more than a longer video as it is easily digestible. It could also be more useful for people who just need information on a specific topic
So happy I'm not coding in C++ anymore. I use Nim and it checks the size of what you pass and chooses by reference or value at compile time. So if you pass an object, it gets sent as reference if it's above a certain threshold. Ints gets passed by value unless you use "var" (mutable reference).
It's also nice to understand the difference between passing by value as 'const uint32_t' or 'uint32_t'. Assuming the argument is in a register, for 'const uint32_t' compiler is safe not to create a copy, while for 'uint32_t' it might allocate another register as this argument might be changed inside a function. For example: uint32_t sum(uint32_t n) { uint32_t sum = 0; while (n > 0) sum += n--; return sum; }
I think, at least for me, it'll be easier to learn and digest your videos in a smaller chunks format like how you use to do C++ tutorial. It'll also be easier to see what specific topic you're covering specifically and if later I need to come back to it, it'll be easier to search as well. Just my thought though. You're still one of my best mentor for programming :)
I also like the idea of splitting the code review into shorter videos. This let‘s one pick the topics of interest quickly and it‘s also easier to „digest“ ;-).
4:10 what if someone adds something big(like a bunch of strings) later in this CustomType? I don't think you want to fix every pass by value after this
I agree that dividing the videos will make them more "digestable" but your content is so good that I wouldn't mind to watch hours of video... I work with C++ nowadays and there some videos that I have watched several times... Thanks for the sharing your amazing knowledge.
Well, you only need to use a const if that reference or data type is needed else ware in the code file. If not then go the value type route if it is not used anywhere else but once to declare a value. Dynamic vs static values used once or more. Depends..
fwiw, the "built-in C++ type" is std::uint32_t. uint32_t is a C alias that most (all major) C++ std library implementations happen to provide, but isn't part of the std
As someone who was actually wondering about this thing in particular I very much aprove the idea of making code reviews as by topic rather than long format they used to be.
This is one of the few times when I actually agree with the design of C++. Since references are mostly transparent you can start with just passing by value and should you change to a more complex object in the future merely change the signature to include a const reference and have little trouble. Some languages make the boneheaded decision of either removing pointers and explicit reference documentation and everything is a reference. Some make the equally boneheaded decision of making references explicit all around which basically means they're just pointers. C++ actually did it right here.
references are just non-nullable pointers with a '.' accessor instead of '->'. who knows why k&r chose '->' instead of '.', but hey now we're stuck with it.
@@Spongmanbecause it’s already used for normal objects. You can also use it with pointers but you gotta dereference them first, leaving you this (*obj).member And it becomes hard to handle when you deal with nested pointers like this: (*(*(*obj).member).attr).val So they invented -> to avoid typing the above obj->member->attr->val
@@Ich_liebe_brezeln you missed my point. I’m saying that k&r could have just used ‘.’ for pointer indirect without ambiguity, since pointers themselves don’t have members. Your last expression could easily have been ‘obj.member.attr.val’.
@@Spongman Oh well, now I get your point and I guess is not a bad idea. Maybe they wanted you to remember that you were dealing with pointers at all times… or didn’t really think that much about that.
Great video as always! The short form videos I think are a lot better because, as you said, the video is more on-topic but not all over the place. However, I think for every code review you should first make a video to overview the project and show the improvements that you would do, maybe explaining briefly (not focus on just one improvement, like the flying dog game video) and then make separate videos on every important topic, explaining it in detail (just like you did in this video).
Personally, both formats (longer video with timestamps) as well as short formats that are just focused on one part are fine. I'd say make the choice based on what gets more views (without having to resort to click-baity titles / screencaps with those "shocked face" looks).
I would pass a custom struct of two 32 bit ints but not a 64 bit int as const ref. Conceptually you are right about the pointer argument, but especially if you make it const, the compiler figures it out to be the same. The custom struct might be 8 bytes now, but could be expanded. It's also just more consistent to have built in types by value and custom stuff const ref. If you use that pattern, passing a custom small type by value will look jarring. Also, as you mentioned yourself, const ref can take an rvalue. This means it doesn't actually have to treat the value as a pointer. Does 2 have a memory location? Not until the compiler decides what to do with it. In fact, const ref tells the compiler to do what is most optimal actually. It might just in place the argument as value wherever it is used for instance.
This format would indeed be better than the massive code reviews, i skip those but this had a title that described what was being talked about and interested me and turned out to be useful.
1:23 not really. The compiler is supposed to try to use the same variable with the same address during compiling. But it never needs to store the variable's address. However, you could still have a problem, where maybe the compiler tried putting the variable on the stack, like at stack[20], and then ended up overwriting that at some point, and had to reaccess it, requiring perhaps upto 4 lines of assembly code, and that could happen for multipler different references of the variable if it is used many times across a large program. That means you might have 100s of extra lines of assembly code being run when a variable is only actually passed into functions like 5 times. At that point, passing it into functions with copy by value would be better. Basically, what I'm saying, is using const reference makes things more difficult for the compiler to optimize around, because it has to get extra smart and remember where everything is and be super careful about not overriding data, which is tricky.
So an aspect that was forgotten here was the speed of the compiler. In large project or large engines it's very important to forward declare things. So if it's a large file that gets pulled to include a variable in an it's not the hotpath seriously consider using a reference so the type can be forwarded declared instead... of course if you are not working with code that is likely to get very big don't worry o much. However c++ compilers can get very slow very quickly if everything is being included in headers which will mean less time for the dev to spend optimizing code that matters.
I think the 'const' is a key part. If the function doesn't need to modify it, then passing by value is fine. (and if the function is const-casting the const away to modify the value, well take that programmer out back and have a talk with them about why they made a function interface that lies) If the function had to modify, then a reference/pointer would be necessary. And if you can pass by value, there should be a bit of performance since each time the function accesses it, it doesn't have to dereference the ptr type over and over again. But that would depend just how often it accesses the parameter.
I listen to your videos as I drive. I like that the signal to noise from your talks is quite high. I’m not talking about static here. I feel like I’ve learned a lot from just listening to you. Some CppCon presentations are very useless to just listen to.
Have you ever seen a case where passing by reference was better due to the reduced set of memory pages being used? I could imagine a case where it would help with TLB hits in the CPU?
AFAIR, x64 compilers like to use __fastcall-based calling convention. In this case **maybe** there's no difference in **memory** used for the value/address transfer (if the value's trivial, less than a register width etc etc..).
I wanna add, if you are consistent with your pointers, a raw pointer can express a non-owning, nullable reference. So it's a great way to have something point to someone without copying it, as long as you can be sure the object stays alive, like it's allocated in main or the encompassing object or something. Otherwise yeah, weak_ptr, but shared is dumb.
This is probably for the Big Video(tm), but passing these small POD types by const reference can have big performance implications beyond the call site. A CPU register does not have an address. Taking the address of a variable means that, for at least some of its lifetime, that variable cannot be stored in a register, since the function you called might want to retain the reference somehow. C++ has a now-deprecated keyword, "register", which makes it an error to take the address of some variable. That was useful behaviour. ADVANCED KNOWLEDGE: Even for a heavyweight object, you may still want to pass it by value under some circumstances, most notably if the callee is going to take a copy. This moves the copy to the caller (which may result in the copy being avoided altogether if the value is an lvalue); the callee can use move semantics rather than copy semantics.
На современных процессорах объекты размером до 8 указателей (32/64 байта) можно смело передавать по значению. Без глубокого копирования указателей на кучу, естественно.
@@DaddyFrosty It is exactly like that. Using const ref gives the compiler perfect information, it can choose to copy or not the uint. People who don't understand this and just spit out the "pass small types by value" ideology from 30 years ago, are frankly clueless. Const reference isn't some promise to use a pointer, it represents the abstraction of a read only object that already exists somewhere.
@@teranyan It depends on the function. Say something like "void multiplyVectorByValue( std::vector& vec, const& val)" - iterating over the vector and multiplying every member by val. here passing by reference can be a serious detriment if the compiler can not proof that "val" is not part of "vec" - cause then it has to re-load val on every iteration, preventing any loop-hoisting or vectorisation.
These smaller code reviews are really great, seeing examples of topics (like passing by value vs reference) within the context of a project is really helpful, especially for someone who's learning and practicing so I know not to make similar mistakes, ect. Plus exactly like you said, just seeing a 40-80 min "Code review for Flying Dog" generally makes me not want to watch so much lol
Regarding monolithic code reviews vs snippets about specific coding issues, I vote for the latter. Bits of info about specific topics seems better (IMHO).
Hey Cherno, I have a very quick question about the behavior of passing in references. At 2:50 you said that "you can actually cast the const away and do whatever you want". At 8:21 you also said that const references can accept 'literal' values because you're passing in a supposed constant value that isn't supposed to change. But what if you do the magic trickery of casting the const away like you said earlier, what's happening behind the scenes, do you just get a random memory address with the literal as the value?
Yes you get a "random" (it'll just be on the stack) memory address with the literal loaded there. It's not the fact that the literal "isn't supposed to change" that allows it to become an rvalue reference but the fact that the value is a temporary that doesn't have storage with a lifetime. The compiler gives it temporary storage such that a pointer to it can exist.
Per the C++ standard, using const_cast to remove constness is only allowed for actual variables that were const to begin with. Otherwise it’s considered “undefined behavior” - which is technically worse than changing a “random” address on the stack.
When you bind an rvalue to a const lvalue reference, the compiler creates a local copy of it. int const& i = 10; Effectively becomes: int const j = 10; int const& i = b;
@@JuvStudios that's the typical implementation, but I'm not sure the standard requires this behavior. In various implementations, the address of the "j" variable you referred to is in a read-only page (and not the stack), which means const_cast and writing can mean an access violation and process crashing. I believe the compiler is also allowed to "put" various const variables with the same value in the same memory location, so const_cast and changing one, might actually change all the rest.
For anybody confused.. for 64-bit application.. one memory address can store up to 64-bits of information or data.. or specifically 2⁶⁴-1..which is an unsigned long.. If you have a class that contains x,y,z values i.e a `3dVector` class.. this data requires 4*3*8=96bits.. So this requires two addresses to store the information.. whereas if you have a pointer variable.. it will only require it to store the information in one address instead of two.. so the process is faster .. And so does the game
The only thing I'll say in defense of the const reference approach is that occasionally I create a method, forget to use a reference on the parameter which makes a copy, then try to modify the parameter inside the method, which does appear to work, but find upon return that the parameter is not modified. That's why I prefer to use const reference instead of value. It makes it more clear for me and for others that the parameter is not intended to be modified and you have to work harder in the code to actually make it happen. Team programming sometimes requires that you write code slightly less optimally in favor of clarity. So in other words, never pass by value, pass by reference, and use const when the value is not to be modified.
I've seen code passing things like string view by const string_view&. Most views, should be passed by value as they often consists of a pointer and length - this usually fits in a wide 128 bit register. This gives the compiler a possibility to optimize. As soon as you use references, you are essentially aliasing data, which hinders some optimizations. And yes, when your type has raw pointers to data you want to shallow copy - because if they hold raw pointers, they should be non-owning views. Someone else owns the data. If not, use shared ptr, and in that case, DON'T create a constructor. You want to eliminate ALL custom constructors always - it should be very rarely when you write custom constructors.
Actually, the answer is... it depends. There are several issues to consider, beyond just the size. For decades, CPUs have been limited in what memory addresses they can directly access. In the old days, it was 2 byte alignment, but it's not uncommon to see access aligned by word size. So, when you access memory, are you going to be fetching it directly, or will the CPU and memory controller access the nearest word and then bit-shift/mask the result to LOOK like you accessed it directly? A compiler does this for you behind the scenes. In assembly, forgetting this was a great way to get a bus error. So, passing a 4 byte integer might seem more efficient, but if the compiler quietly pads it to 8 bytes for memory alignment efficiency anyways, it's not. The other issue is threading. If you pass in a value, and the code you are calling then uses that with threads down the road, your value might be stale and out of sync with the original. If you pass by reference, you may need to deal with thread locking when accessing it, but you can ensure that the value stays in sync. Of course, if you know none of this applies, then of course passing by value is simpler and easier.
It's not just about the size. The compiler is also allowed to perform optimizations on value parameters that it's not allowed to do on reference parameters. Specifically everytime that you call another function, from within the function that received it's parameters by reference, the compiler must reload the value of that referenced variable from memory. Because the referenced variable might have been changed. This problem doesn't exist for value parameters because they're local variables to which nothing else has access. Unless you give it access. And because calling a function that takes referenced to its argument gives access the same problem exists for the caller! I.e. references to cheap to copy objects are expensive on both sides of the function call barrier!
"the compiler must reload the value of that referenced variable from memory." No. That depends on the function and the surrounding context but by no means is the compiler forced to do so.
@@ABaumstumpf sure it depends. Specifically the value of that referenced variable needs to be used after the function call for such a reload to be necessary
Oh no, watching The Cherno's videos gets me excited about copying memory and pointers and whatnot in C++, I don't want to dive into this rabbit hole again *starts editing makefile* oooooooh nooooooooooo what have you dooooooooooone Chernoooooooooooo
Love this style of broken-up code review, but it would be nice if there was some indication that this is actually a code review, like maybe on the thumbnail
The size of the pointer/reference isn't the issue. The indirection is a performance consideration as it will cause a pointer indirection when the data is accessed. I would recommend any basic types including __m128 be passed by value as the first 4 int and seperately float/simd arguments (depending on ABI) will be passed in registers, without copying to the stack and therefore pass a pointer. On Windows the x64 ABI prevents any structs larger than 64-bit from using registers. On other 64-bit operating systems and platforms a struct with two 64-bit ints can be passed through registers. This is why new C++ types such as std::span and std::expected perform poorly on Windows and Xbox. This is also the case with returning structs/classes by value.
Beyond this, its just unintuitive behavior to ref a constant. Reffing a constant sort of mimics a static readonly. And if you're seeking to save memory in this way then a static readonly is the way to go. If we look into why string.Empty is a static readonly rather than a constant, it has to do with using it from unmanaged code not marking it as a literal. _"The Empty constant holds the empty string value. We need to call the String constructor so that the compiler doesn't mark this as a literal. Marking this as a literal would mean that it doesn't show up as a field which we can access from native."_
You really should NOT assume what the compiler will optimise and what not - almost certainly you will be wrong. With a single "const int&" almost certainly the assembly will be identical as the compiler can see that there are no other parameters that can alias this and so it is free to just pass it by value, by ref, or with whatever method it choses. Also a ref is NOT required to have and address, it is not required to take up memory or be a pointer. On the contrary it can even just be a register if for example the type referenced is just a temporary r-value. So saying that passing a "Uint32&" will take 8 byte is just objectively wrong - it can be anything from 0 to 8 (or whatever) bytes. Passing small values by ref can be a huge detriment when the compiler can not rule out aliasing, but it can also free it up to do a lot of optimisation like eliminating the function-call all together. These small thing should be the last of your the last thing to worry about.
My opinion on the const ref is that it doesnt really matter, in fact, matter the case, it can be even better because its like ensuring that syntactically he wont use that variable to write to so he can only read from. As per the performance, with /O2 its almost identical, performance is the same. In one case it is performing operations on the registers directly (pass by value) and in the other its just dereferencing the address (pass by ref). So in both cases its the same amount of instructions, now the microinstructions can differ but i dont really think that is a factor if you want your code to look more "clean?"
The optimizer can't break the ABI, so a reference is still a reference, your still forcing a value which should be a register into memory (often on the stack), your still preventing the compiler from optimizing it when it can't see into other functions, while most code doesn't need to be fast there are definately cases where pass by value will make a difference.
Here’s a hot take; put the const to the right of the variable name. Imo it’s more correct, despite being more uncommon. Const is parsed from right to left, with the only exception being if it’s leftmost, then it’s parsed the other way. So people are actually doing an exception to the rule all the time.
Absolutely good idea. Divide the videos into multiple topics 🙏
+1
+1
+1
Yup. This is the first code review video I've watched, so it definitely works 😃.
+1
A completely agree with you that passing primitive data types by reference is not a great idea. It will most likely prevent compiler optimizations. However, I think its probably a good idea to pass custom types by reference, even if they are small. The reason being that their size (and internal behavior) can easily change in the future, without the programmer later realizing that these function parameters all need updated to reference types. After 20 years of programming, one thing I've learned is to treat everything I've touched as unstable and dynamic.
damn
I heartily approve of doing the code reviews by topic. If it tells you anything, this is the very first one of your code reviews I've ever watched--specifically because it wasn't ridiculously long, and I could see in the title that it was about something I might find useful.
You might get fewer views per individual video this way, but I have a feeling you'll get *way* more total views overall.
I don't agree, I think he'll get even more views per video because they'll be short and more focused.
Same here. I once started like a 1h video of code review, but quickly I got lost and never finished.
Instead, for this quick video I already entered knowing what was the topic, was direct to the point, easier to understand and, well, I saw to the end.
I agree with this
This was definitely more easily digestible and focussed than 40 minutes of code review that goes in five different directions. I smashed like so hard on this that I almost dropped my phone.
this is absolutely a better format, not only because my attention span is garbage
but also it really helps to build a library of "advice" video that's easily searchable even without the context of the code review
this kind of advice is quite rare in a normal beginner tutorial and StackOverflow can be quite asinine about it
this way your video can reach a broader audience who just need specific advice
I like the idea of splitting the code review video by topic! It taught me a lot. Hope to see you sharing more videos like this in the future.
100% love the shorter videos. Recently I haven't been able to find the time watch longer code review videos, so breaking them down makes it much easier
This format is a good idea, and you could also add this video to the C++ series as it covers a pretty interesting topic that fits into it ! Cheers
Heheheh... i know fools who const reference everything even boolean types and I'm like... 🤦♂🤦♂🤦♂
Divide by multiple topics, that will help your channel on the long term as more videos will popup on RUclips search. And that will help less skilled programmers(like me) to learn specific topics.
I really like the short, by topic code review!
That "it gots darker here" got me 😂
Just look at the assembly code my friend, I bet you will get a couple of surprises, particularly with private methods. Hint: the compiler does not always honour the argument passing convention you specified
can you go into a bit more detail? what exactly are you referring to
@@andyyy1094 I mean that you can write code meant to pass or return parameters by value, particularly long structs, but the compiler may decide to pass them by constant reference if such parameters are not modified inside the function and the function is contained in the same translation unit (C static function, or C++ private method). Furthermore, even if the function is not private, some compilers can go a step further and create two versions of the same function, one to be called (often inlined) using references when invoked from the same translation unit (regardless of what passing convention you specified), and another one ready for external calls. However, what is said in the video is correct and programmers should keep with good practices and never assume that the compiler will optimise everything
Dont trust the compiler on everything, especially if you want to run things in debug mode.
@@urisinger3412 What you mean by don't trust the compiler? Compilers are by far the most trustable pieces of software in existence. Can't imagine the mess if it wasn't like that
@@RelayComputer dont trust the compiler to optimize every stupid thing you do, they dont catch everything and those little things can add up
I just want to join all those that have said that it is an excellent idea to divide the code reviews into smaller videos focusing on one concept. It is super useful!!! Also, you get the opportunity to explain the same concept in multiple and different code sets which will greatly help understand better the concept, you know, by looking at it from different angles and use cases.
making short code review responses that address very specific topics would be perfect for...well, shorts!
I've seen a couple channels try out short "tips & tricks" videos around code and they've been very useful!!
Great Idea go for it. That is the exact reason why I watch long code review videos.
At any random time in a video, you give a specific knowledge for a specific situation that normal learning seriea doesn't even think about to teach us.
For example this video enlighten me about r and l-values. Thank you for that. Learned C++ with your C++ series years ago and now I can develop custom tools for the games I make with Unreal Engine.
Splitting a long code review into more specific and shorter videos is a fantastic idea. As you said anyone could focus on particular topics that may be intersted in and understand a topic better without being overwhelmed
This video is good advice, it's basically F.16 in the cpp core guidelines: For “in” parameters, pass cheaply-copied types by value and others by reference to const. F.15 has a really good diagram that illustrates all the choices.
IMO it's a good idea to keep your argument definitions 'const', even when receiving value types. Especially in larger methods it's useful to know for certain that a given argument hasn't changed.
Also, don't use 'new', 'array'/'vector' would trivially give you the all-important move constructor for that buffer.
Very good idea - the fact to show more shorted (but specific) videos is (imho) more interesting and more appealing than big review videos.
@thecherno3 OMG thank you! What did I won?! I can’t believe it! It is amaaaaaaaaazing! I hope you’re not a fake OMG! I hope it is a house! Is it a house? Or a 35 levels building in the middle of Central Park?! Or, more amazing, 15kg of twinkies!!!! I hope this is not fake OMG! WHAT IS A DM? HOW TO DM? WHAT IS THE PURPOSE OF LIFE?! WILL « TRON 3 » BE A GOOD TRON SEQUEL? SO MUCH QUESTIONS!!!!!!!
Explaining the "why" of anything is extremely valuable, at least imho. throughout all of your content that I have watched, the most enjoyable is when you explain quite trivial things but explain the reasoning at a really low level.
Oh, my dear Cherno.
1. Roughly at 1:20 you said that const reference is converted to ptr. Actually, not just a "ptr *", but "ptr * const".
2. Passing argument to a function depends on ABI. If your ABI declares that all arguments must be passed via stack - then yes, small types converted to pointer will take more memory.
In old ABIs agruments was always passed via stack (see Intel x86 ABI as an example).
In modern ABIs arguments mostly are passed via registers which always have same size. Only data with sizes higher than register size always passes via stack.
The main difference here is number of accesses to a memory holding the value and it will be "long" if variable located in different memory page, which will have to be kept in RAM.
3. At ~4:59 you saying that "type is not trivially copyable" is an issue of passing by value. This is not the sole reason. If your type is trivially copyable, but is a big sized (too many members/fields) - this is also an issue for passing by value, because you'll eat all the stack.
clearly breaking abi is not an issue, if you're still in development, just recompile the binaries
With (2) if your passing by reference then the reference needs to point to a place in memory, if this is something that was previously just a local variable this is going to force it onto the stack just so that it can be passed as a reference to the called function. ABIs difffer a fair bit it's not just size that impacts things but also if the type is trivial, if the ABI splits structs (like System V), then you have some which don't have many registers for arguments like MS ABI
Also 32-bit x86 is pretty much dead these days for any game you ship and not worth worrying about, everything targets 64-bit now.
@@Raspredval1337 You can break ABI only if you write the call by hands in Assembly language. ABI is the convention which is followed by compiler, not the developer.
@@malckhazarsteamcat9817 You mean the platform. The compiler just targets the platform, so if the convention used is wrong it's because the compiler targeted the platform incorrectly.
@@anon_y_mousse No, you mistook.
ABI (or Application Binary Interface) is a convention declaring the order how calls should be made in Assembly. It is included in that you call a "platform", but, technically, it does not restrict you to use other orders of calls inside your code. The problem will arise only after you violate ABI used in compilation of other binary parts of your app - libraries, system calls, etc.
Inside your code no one can restrict you from writing the call to your function in Assembly.
The part of the video where you said you should probably make an entire video about, I think it was around 2:15, that would be very helpful. The CPU is still very much unknown to me. And individual videos would be great especially as a playlist too.
I agree that having separate videos on specific topics is better. I'm currently struggling with my side project in C++, and I can say I'm already pretty comfortable with templates and even type traits-topics considered quite complex-even though I've never used them before. But I still often struggle with basic things like passing by value, reference, or pointer, or knowing when it's better to use shared_ptr or unique_ptr, move semantics, etc. Probably that's because I've been employed as a Python developer for years, and I deal with abstractions and polymorphism with dynamic types every day, but some basic C++ concepts are new to me and others like me.
Definitely prefer this format more than a longer video as it is easily digestible. It could also be more useful for people who just need information on a specific topic
So happy I'm not coding in C++ anymore. I use Nim and it checks the size of what you pass and chooses by reference or value at compile time. So if you pass an object, it gets sent as reference if it's above a certain threshold. Ints gets passed by value unless you use "var" (mutable reference).
Yes!!! Please do that! Splitting it into parts and explaining different topic on each video would be so much helpful!
It's also nice to understand the difference between passing by value as 'const uint32_t' or 'uint32_t'. Assuming the argument is in a register, for 'const uint32_t' compiler is safe not to create a copy, while for 'uint32_t' it might allocate another register as this argument might be changed inside a function.
For example:
uint32_t sum(uint32_t n) { uint32_t sum = 0; while (n > 0) sum += n--; return sum; }
I think, at least for me, it'll be easier to learn and digest your videos in a smaller chunks format like how you use to do C++ tutorial. It'll also be easier to see what specific topic you're covering specifically and if later I need to come back to it, it'll be easier to search as well. Just my thought though. You're still one of my best mentor for programming :)
I look forward to your bigger video on this :) I also enjoy this style of code review more, it's more descriptive to what the video goes over
As you said, a big video on this would be really cool. Thanks for all your work :)
9:43 yes! this is a fantastic idea. I only clicked on this because the title was interesting. I never click on code review videos
Good idea. Shorter videos but more specific targeting a given concept or issue. It 's gonna be more interesting.
Really valid topic! Varies in a lot of things - threads and coroutines make things slightly trickier, but nice video
I also like the idea of splitting the code review into shorter videos. This let‘s one pick the topics of interest quickly and it‘s also easier to „digest“ ;-).
i really liked this one. It's short and it's about one specific topic.
4:10 what if someone adds something big(like a bunch of strings) later in this CustomType? I don't think you want to fix every pass by value after this
I agree that dividing the videos will make them more "digestable" but your content is so good that I wouldn't mind to watch hours of video... I work with C++ nowadays and there some videos that I have watched several times... Thanks for the sharing your amazing knowledge.
Such a good idea!
Please, more of these short videos!
Well, you only need to use a const if that reference or data type is needed else ware in the code file. If not then go the value type route if it is not used anywhere else but once to declare a value.
Dynamic vs static values used once or more. Depends..
fwiw, the "built-in C++ type" is std::uint32_t. uint32_t is a C alias that most (all major) C++ std library implementations happen to provide, but isn't part of the std
static_assert(std::is_trivially_copyable_t); Type trait can be used to test if your type is trivially copyable.
As someone who was actually wondering about this thing in particular I very much aprove the idea of making code reviews as by topic rather than long format they used to be.
nice video. Anyway I watched a lot of your videos and one think that i will remember for sure is "kind of", "kind of", "kind of"
This is one of the few times when I actually agree with the design of C++. Since references are mostly transparent you can start with just passing by value and should you change to a more complex object in the future merely change the signature to include a const reference and have little trouble. Some languages make the boneheaded decision of either removing pointers and explicit reference documentation and everything is a reference. Some make the equally boneheaded decision of making references explicit all around which basically means they're just pointers. C++ actually did it right here.
references are just non-nullable pointers with a '.' accessor instead of '->'. who knows why k&r chose '->' instead of '.', but hey now we're stuck with it.
@@Spongmanbecause it’s already used for normal objects. You can also use it with pointers but you gotta dereference them first, leaving you this
(*obj).member
And it becomes hard to handle when you deal with nested pointers like this:
(*(*(*obj).member).attr).val
So they invented -> to avoid typing the above
obj->member->attr->val
@@Ich_liebe_brezeln you missed my point. I’m saying that k&r could have just used ‘.’ for pointer indirect without ambiguity, since pointers themselves don’t have members.
Your last expression could easily have been ‘obj.member.attr.val’.
@@Spongman Oh well, now I get your point and I guess is not a bad idea. Maybe they wanted you to remember that you were dealing with pointers at all times… or didn’t really think that much about that.
Great video as always!
The short form videos I think are a lot better because, as you said, the video is more on-topic but not all over the place.
However, I think for every code review you should first make a video to overview the project and show the improvements that you would do, maybe explaining briefly (not focus on just one improvement, like the flying dog game video) and then make separate videos on every important topic, explaining it in detail (just like you did in this video).
Personally, both formats (longer video with timestamps) as well as short formats that are just focused on one part are fine. I'd say make the choice based on what gets more views (without having to resort to click-baity titles / screencaps with those "shocked face" looks).
Holy shit I searching for something like this just a day or two ago. Chreno-sama smiles upon me 🙏
I found it useful, and I am more willing to watch a short video about a specific problem/thing in the code.
I would pass a custom struct of two 32 bit ints but not a 64 bit int as const ref. Conceptually you are right about the pointer argument, but especially if you make it const, the compiler figures it out to be the same. The custom struct might be 8 bytes now, but could be expanded. It's also just more consistent to have built in types by value and custom stuff const ref. If you use that pattern, passing a custom small type by value will look jarring.
Also, as you mentioned yourself, const ref can take an rvalue. This means it doesn't actually have to treat the value as a pointer. Does 2 have a memory location? Not until the compiler decides what to do with it. In fact, const ref tells the compiler to do what is most optimal actually. It might just in place the argument as value wherever it is used for instance.
thank you for clearing this confusion on my part!!
Yes, please. Short videos on a specific topics
Good idea Cherno! Clicked on this vid because it was not 01h and 40min long for example =)
This format would indeed be better than the massive code reviews, i skip those but this had a title that described what was being talked about and interested me and turned out to be useful.
1:23 not really. The compiler is supposed to try to use the same variable with the same address during compiling. But it never needs to store the variable's address. However, you could still have a problem, where maybe the compiler tried putting the variable on the stack, like at stack[20], and then ended up overwriting that at some point, and had to reaccess it, requiring perhaps upto 4 lines of assembly code, and that could happen for multipler different references of the variable if it is used many times across a large program. That means you might have 100s of extra lines of assembly code being run when a variable is only actually passed into functions like 5 times. At that point, passing it into functions with copy by value would be better. Basically, what I'm saying, is using const reference makes things more difficult for the compiler to optimize around, because it has to get extra smart and remember where everything is and be super careful about not overriding data, which is tricky.
So an aspect that was forgotten here was the speed of the compiler. In large project or large engines it's very important to forward declare things. So if it's a large file that gets pulled to include a variable in an it's not the hotpath seriously consider using a reference so the type can be forwarded declared instead... of course if you are not working with code that is likely to get very big don't worry o much.
However c++ compilers can get very slow very quickly if everything is being included in headers which will mean less time for the dev to spend optimizing code that matters.
Never thought of this whenever using passing by const reference. Cherno sir 👌
I think the 'const' is a key part. If the function doesn't need to modify it, then passing by value is fine. (and if the function is const-casting the const away to modify the value, well take that programmer out back and have a talk with them about why they made a function interface that lies)
If the function had to modify, then a reference/pointer would be necessary. And if you can pass by value, there should be a bit of performance since each time the function accesses it, it doesn't have to dereference the ptr type over and over again. But that would depend just how often it accesses the parameter.
I listen to your videos as I drive. I like that the signal to noise from your talks is quite high. I’m not talking about static here. I feel like I’ve learned a lot from just listening to you.
Some CppCon presentations are very useless to just listen to.
Hi Cherno. Love your work! Could you please do a c++ video on implementing a finite state machine?
I like the idea of splitting up the code review my topic.
Have you ever seen a case where passing by reference was better due to the reduced set of memory pages being used? I could imagine a case where it would help with TLB hits in the CPU?
AFAIR, x64 compilers like to use __fastcall-based calling convention. In this case **maybe** there's no difference in **memory** used for the value/address transfer (if the value's trivial, less than a register width etc etc..).
Hi, can you please make a detailed video of how virtual table works in the context of virtual functions?
Very insightful as always Cherno thanks!
Multiple topic videos per review would be great!
I wanna add, if you are consistent with your pointers, a raw pointer can express a non-owning, nullable reference. So it's a great way to have something point to someone without copying it, as long as you can be sure the object stays alive, like it's allocated in main or the encompassing object or something. Otherwise yeah, weak_ptr, but shared is dumb.
By topic sounds (and works in this instance) great!
This is probably for the Big Video(tm), but passing these small POD types by const reference can have big performance implications beyond the call site. A CPU register does not have an address. Taking the address of a variable means that, for at least some of its lifetime, that variable cannot be stored in a register, since the function you called might want to retain the reference somehow.
C++ has a now-deprecated keyword, "register", which makes it an error to take the address of some variable. That was useful behaviour.
ADVANCED KNOWLEDGE: Even for a heavyweight object, you may still want to pass it by value under some circumstances, most notably if the callee is going to take a copy. This moves the copy to the caller (which may result in the copy being avoided altogether if the value is an lvalue); the callee can use move semantics rather than copy semantics.
I think dividing code review based on topics would be great!
На современных процессорах объекты размером до 8 указателей (32/64 байта) можно смело передавать по значению. Без глубокого копирования указателей на кучу, естественно.
Another thing to consider is that unnecessary passing by pointer/reference can also prevent the compiler from making certain microoptimizations
Oh shit I thought Const ref would help as opposed to impede that
@@DaddyFrosty It is exactly like that. Using const ref gives the compiler perfect information, it can choose to copy or not the uint. People who don't understand this and just spit out the "pass small types by value" ideology from 30 years ago, are frankly clueless. Const reference isn't some promise to use a pointer, it represents the abstraction of a read only object that already exists somewhere.
@@teranyan C++ Core Guidelines F.16: For “in” parameters, pass cheaply-copied types by value and others by reference to const
@@teranyan It depends on the function.
Say something like "void multiplyVectorByValue( std::vector& vec, const& val)" - iterating over the vector and multiplying every member by val. here passing by reference can be a serious detriment if the compiler can not proof that "val" is not part of "vec" - cause then it has to re-load val on every iteration, preventing any loop-hoisting or vectorisation.
These smaller code reviews are really great, seeing examples of topics (like passing by value vs reference) within the context of a project is really helpful, especially for someone who's learning and practicing so I know not to make similar mistakes, ect. Plus exactly like you said, just seeing a 40-80 min "Code review for Flying Dog" generally makes me not want to watch so much lol
love seeing this short and sweet!!
So, if we can efficiently pass stuff by value that is
Regarding monolithic code reviews vs snippets about specific coding issues, I vote for the latter. Bits of info about specific topics seems better (IMHO).
Agree with others, I think the length and content of this video is much more preferable to your longer videos. Easier to digest etc.
Fantastic topic, helps a lot about programming futures. Nice video
Hey Cherno, I have a very quick question about the behavior of passing in references. At 2:50 you said that "you can actually cast the const away and do whatever you want". At 8:21 you also said that const references can accept 'literal' values because you're passing in a supposed constant value that isn't supposed to change. But what if you do the magic trickery of casting the const away like you said earlier, what's happening behind the scenes, do you just get a random memory address with the literal as the value?
Yes you get a "random" (it'll just be on the stack) memory address with the literal loaded there. It's not the fact that the literal "isn't supposed to change" that allows it to become an rvalue reference but the fact that the value is a temporary that doesn't have storage with a lifetime. The compiler gives it temporary storage such that a pointer to it can exist.
Per the C++ standard, using const_cast to remove constness is only allowed for actual variables that were const to begin with. Otherwise it’s considered “undefined behavior” - which is technically worse than changing a “random” address on the stack.
When you bind an rvalue to a const lvalue reference, the compiler creates a local copy of it.
int const& i = 10;
Effectively becomes:
int const j = 10;
int const& i = b;
@@JuvStudios that's the typical implementation, but I'm not sure the standard requires this behavior. In various implementations, the address of the "j" variable you referred to is in a read-only page (and not the stack), which means const_cast and writing can mean an access violation and process crashing. I believe the compiler is also allowed to "put" various const variables with the same value in the same memory location, so const_cast and changing one, might actually change all the rest.
For anybody confused..
for 64-bit application..
one memory address can store up to 64-bits of information or data..
or specifically 2⁶⁴-1..which is an unsigned long..
If you have a class that contains x,y,z values i.e a `3dVector` class..
this data requires 4*3*8=96bits..
So this requires two addresses to store the information..
whereas if you have a pointer variable..
it will only require it to store the information in one address instead of two..
so the process is faster ..
And so does the game
with modern cpu's (
maybe you could divide the video by topics, but also upload the full one later. I actually enjoy watching the full video
The only thing I'll say in defense of the const reference approach is that occasionally I create a method, forget to use a reference on the parameter which makes a copy, then try to modify the parameter inside the method, which does appear to work, but find upon return that the parameter is not modified. That's why I prefer to use const reference instead of value. It makes it more clear for me and for others that the parameter is not intended to be modified and you have to work harder in the code to actually make it happen. Team programming sometimes requires that you write code slightly less optimally in favor of clarity. So in other words, never pass by value, pass by reference, and use const when the value is not to be modified.
Exactly this is, how i also got "teached" how to handle this. This video actually surprised me saying const ref arguments are bad ^^
But wouldn't just const work in this case without the reference?
I've seen code passing things like string view by const string_view&. Most views, should be passed by value as they often consists of a pointer and length - this usually fits in a wide 128 bit register. This gives the compiler a possibility to optimize. As soon as you use references, you are essentially aliasing data, which hinders some optimizations.
And yes, when your type has raw pointers to data you want to shallow copy - because if they hold raw pointers, they should be non-owning views. Someone else owns the data.
If not, use shared ptr, and in that case, DON'T create a constructor. You want to eliminate ALL custom constructors always - it should be very rarely when you write custom constructors.
Honestly surprised this isn't handled by the compiler.
Actually, the answer is... it depends. There are several issues to consider, beyond just the size. For decades, CPUs have been limited in what memory addresses they can directly access. In the old days, it was 2 byte alignment, but it's not uncommon to see access aligned by word size. So, when you access memory, are you going to be fetching it directly, or will the CPU and memory controller access the nearest word and then bit-shift/mask the result to LOOK like you accessed it directly? A compiler does this for you behind the scenes. In assembly, forgetting this was a great way to get a bus error.
So, passing a 4 byte integer might seem more efficient, but if the compiler quietly pads it to 8 bytes for memory alignment efficiency anyways, it's not.
The other issue is threading. If you pass in a value, and the code you are calling then uses that with threads down the road, your value might be stale and out of sync with the original. If you pass by reference, you may need to deal with thread locking when accessing it, but you can ensure that the value stays in sync.
Of course, if you know none of this applies, then of course passing by value is simpler and easier.
I like the Qt style: pass c++ (std)types by value, Qt objects by reference, and Qt objects where you transfer ownership by ptr
It's not just about the size. The compiler is also allowed to perform optimizations on value parameters that it's not allowed to do on reference parameters. Specifically everytime that you call another function, from within the function that received it's parameters by reference, the compiler must reload the value of that referenced variable from memory. Because the referenced variable might have been changed. This problem doesn't exist for value parameters because they're local variables to which nothing else has access. Unless you give it access. And because calling a function that takes referenced to its argument gives access the same problem exists for the caller!
I.e. references to cheap to copy objects are expensive on both sides of the function call barrier!
"the compiler must reload the value of that referenced variable from memory."
No.
That depends on the function and the surrounding context but by no means is the compiler forced to do so.
@@ABaumstumpf sure it depends. Specifically the value of that referenced variable needs to be used after the function call for such a reload to be necessary
Oh no, watching The Cherno's videos gets me excited about copying memory and pointers and whatnot in C++, I don't want to dive into this rabbit hole again *starts editing makefile* oooooooh nooooooooooo what have you dooooooooooone Chernoooooooooooo
Love this style of broken-up code review, but it would be nice if there was some indication that this is actually a code review, like maybe on the thumbnail
very deep video about basics , thx
I liked this format, short videos are better to be referred later too
The size of the pointer/reference isn't the issue. The indirection is a performance consideration as it will cause a pointer indirection when the data is accessed. I would recommend any basic types including __m128 be passed by value as the first 4 int and seperately float/simd arguments (depending on ABI) will be passed in registers, without copying to the stack and therefore pass a pointer. On Windows the x64 ABI prevents any structs larger than 64-bit from using registers. On other 64-bit operating systems and platforms a struct with two 64-bit ints can be passed through registers. This is why new C++ types such as std::span and std::expected perform poorly on Windows and Xbox. This is also the case with returning structs/classes by value.
I love this video format!
Good idea to split the review up👍
Beyond this, its just unintuitive behavior to ref a constant. Reffing a constant sort of mimics a static readonly. And if you're seeking to save memory in this way then a static readonly is the way to go. If we look into why string.Empty is a static readonly rather than a constant, it has to do with using it from unmanaged code not marking it as a literal.
_"The Empty constant holds the empty string value. We need to call the String constructor so that the compiler doesn't mark this as a literal.
Marking this as a literal would mean that it doesn't show up as a field which we can access from native."_
I prefer to watch the code review split into smaller topics. Makes it easier to process and I search it, should I ever want to rewatch it.
You really should NOT assume what the compiler will optimise and what not - almost certainly you will be wrong.
With a single "const int&" almost certainly the assembly will be identical as the compiler can see that there are no other parameters that can alias this and so it is free to just pass it by value, by ref, or with whatever method it choses. Also a ref is NOT required to have and address, it is not required to take up memory or be a pointer. On the contrary it can even just be a register if for example the type referenced is just a temporary r-value.
So saying that passing a "Uint32&" will take 8 byte is just objectively wrong - it can be anything from 0 to 8 (or whatever) bytes.
Passing small values by ref can be a huge detriment when the compiler can not rule out aliasing, but it can also free it up to do a lot of optimisation like eliminating the function-call all together.
These small thing should be the last of your the last thing to worry about.
9:42 that's a good idea everyone will be able to watch those small videos and learn something new, like i did in this video ;)
My opinion on the const ref is that it doesnt really matter, in fact, matter the case, it can be even better because its like ensuring that syntactically he wont use that variable to write to so he can only read from.
As per the performance, with /O2 its almost identical, performance is the same. In one case it is performing operations on the registers directly (pass by value) and in the other its just dereferencing the address (pass by ref). So in both cases its the same amount of instructions, now the microinstructions can differ but i dont really think that is a factor if you want your code to look more "clean?"
The optimizer can't break the ABI, so a reference is still a reference, your still forcing a value which should be a register into memory (often on the stack), your still preventing the compiler from optimizing it when it can't see into other functions, while most code doesn't need to be fast there are definately cases where pass by value will make a difference.
Here’s a hot take; put the const to the right of the variable name. Imo it’s more correct, despite being more uncommon. Const is parsed from right to left, with the only exception being if it’s leftmost, then it’s parsed the other way. So people are actually doing an exception to the rule all the time.
West Const for life
Now, how does one distinguish a rvalue reference and a lvalue that happens to be a reference when calling foo(T&&)?
I think the problem is that the ide, highlights it if you dont use a const reference, i just noticed it as i was working on my project