Yeah, the naming could be done better. But then naming things is hard. To it's credit, std::move was a good enough name to make all the people involved in it's design and implementation understand each other. 🤷♂️
Great video once again. I have one small addition: when declaring a move constructor and a move assignment operator it is a good idea to mark is as `noexcept`. Most of the time this should be possible because you're just moving some pointers around. I think main the reason for this is the MSVC compiler which (for some odd reason) can still call the copy constructor even when you do a std::move if the move constructor is not marked as noexcept. Generally, it is not required but in most cases it is possible and it is a good coding practice. A good follow up episode could be perfect forwarding, which is basically extending value semantics to generic types. The problem is that in order to understand perfect forwarding one must first understand templates, which is a huge topic on it own.
Thanks Zamfir! I did not know about the MSVC quirk! 😳 Do you have a link for me to read more about it? That sounds really interesting! As for noexcept I did not include it because we did not talk about exceptions yet 🤷♂️ And you're right about perfect forwarding: we need templates for that. It's coming but later 😬
Thanks a lot! Do you have any plan on touching on forwarding/universal references? How much do you use them on your daily work? I sort of feel it's mostly used by library developers Edit: Oh I read the comments; you do. :)
That is a great question! I will cover forwarding later as I believe it is a much more advanced thing. And yes, I would agree that this is mostly for library development. I did use it a bunch of times but rarely in production code to be honest 😬
Wish I saw this video a week ago, I was having trouble with objects disappearing from vectors after they move in memory. The solution for me was to use a copy constructor, because the constructor isn't called when the object is copied.
I always thought of rvalues as temporary values, which are destroyed as soon as they go out of scope. This is why you can "scavange" its resources. But unfortunately how c++ works is that rvalues can bind to lvalues, basically violating that assumption. Its making a possibly short lived object live longer than it actually is. This will cause them to become possibly dangling references!
@@CodeForYourself With current c++ rules, this would be allowed to compile: auto even = std::vector {1,2,3} | std::views::filter([](int i) { return i%2 == 0; }); ranges are evaluated lazily, thus the ranges filter will hold an lvalue reference to the vector. with current c++ rules this would be allowed as rvalues can be extended to lvalues. But compilers are smart and this does not compile since the filter will hold a dangling reference. Lets talk about temporary lifetime extension. I can use temporary lifetime extension to store either a value or a reference in a reference So thanks to temporary lifetime extension i can this: A const& a = A(); or this: A const& a = get_A(); // where get_A( ) returns a reference. this will hold rvalues just fine as well as regular values trough Temporary lifetime extension. (In a way auto const& is a universal storage for any value or reference.) Consider a getter for some struct A, that returns a const A&&, so can steal its contents if needed. Or use std::move. const A& a = std::min(a1.getA(), a2.getA()); (assume As are comparable) The input of std::min are 2 rvalues references and due to the implementation of std::min they accepted as lvalues and thus get returned into lvalue references, and will also be returned as lvalue reference, as far as temporary lifetime extension is concerned there is nothing to extend the lifetime of. This means a dangles. Maybe we can write our own min using universal references and forward them out of min. And if both of them are rvalue references we can still steal their resources after min if needed. but the problem is a still dangles. you can assign an rvalue reference to an lvalue reference but since its actually an rvalue reference it gets destroyed and you get the dangling reference. Consider a getter for some struct A, that either returns a an A, a const A&. (Or even a A const&&) Ideally in some generic code i want to hold either the value or the reference. decltype(auto) foo() { const A& a = get_A(); } if get_A() returns a value, we get a dangeling reference due to it being an rvalue. and using auto foo() { const A& a = get_A(); } this will always copy, because auto is always a value. This is a problem due to Temporary lifetime extension lying about its value. As far as the type system is concerned get_A() always returns a reference. When in fact it could be a value. This could actually be solved by making it illegal to bind rvalue references to lvalue references.
Yeah, it would be cool. But there is a reason why it is done the way it's done. We would have to record the fact that the data was moved from somewhere 🤷♂️
The main problem is when you move data conditionally. if(something) a = std::move(b); After this point you may or you may not use `b` and the compiler cannot help you because the decision happens at runtime. But I agree that in cases where the compiler *can* prove that the object is moved-from then it should print a warning or something. Actually, you can still use a moved-from object but there are only 2 allowed operations: destruction and assignment. Meaning you can safely destroy the "empty" object or you can give it a new meaningful value by assigning to it.
@@CodeForYourself Not a good idea, how would you swap two variables? Currently, it is legal to access an object after it is moved from, it should be in valid, but unspecified state. That means, it could be assigned to, and all const member functions could be called on it (because they have no pre-condition on whether they could be called or not). Many standard library types are in valid and in SPECIFIED state after being moved from, typically in an empty state. Consider following example. int main() { huge_type a; huge_type b; huge_type tmp; a = make_something_huge(1); b = make_something_huge(2); /* swap begin */ tmp = std::move(a); a = std::move(b); /* oh no, use-after-move of variable a according to your rules */ b = std::move(tmp); /* oh no, use-after-move of variable b according to your rules */ /* swap end */ assert(tmp.is_empty()); /* oh no, use-after-move of variable tmp according to your rules */ } Remember, assignment operator is just syntactic sugar for a member function. You can imagine the previous example using a.assign(std::move(b)) instead. What would be better is destructive move. After an object is moved-from it is destroyed (its destructor is being run) and although its name is still in scope, referring to it is a compiler error. Example: int main() { huge_type a; huge_type b; a = make_something_huge(1); b = make_something_huge(2); a = std::destructive_move(b); b = make_something_huge(3); /* oh no, compiler error, b was already destroyed, according to my rules */ }
Constructive criticism. I'm not sure if it's to mask a blend + jump cut, but I would recommend not doing that zoom things that's become so popular lately. I can tell it's not your intention, but I find it oddly and indeed, shamelessly domineering yet sneaky. It's just really kind of gross even if it's meant for emphasis. Though my brain is processing this video as a flat image with inferred depth (your head becomes larger), fact is, IRL if that was happening it would mean one of us suddenly leaned in closer to the other's face, then back, then lean, then back. It's weird. Don't know who started it, but I dislike it. A lot. When weird and mediocre is the norm, different may well be simply better. Be different. Resist decay.
I see the point you're making here and mostly agree with it. The amount of zooming in is over excessive in this video. I will probably settle somewhere in the middle in future. I still like how zoom works for important moments but will try not to use indiscriminately.
@@CodeForYourself I think, if we try to put it in practical and mechanical terms, a primary issue is in how activation of threat processing interacts with working memory and other short term memory state. A lot of research shows, regardless of context, there is a hard bias towards interpreting any object approaching as potentially threatening. The unpredictability and suddenness of the zoom acts as a neurological "interrupt" signal, which eill either rapidly store existing state in a poorly structured form, or will dump existing state. It's not as extreme as suddenly registering that you're looking into the face of a lion or something through the tall grass, but the underlying fundamentals are there. Personally I think high contrast works better for emphasis than the startle response. So information is easily organized into a tree-like dendritic structure, which naturally fits into the associative web-like structure of long term storage.
I think you're trying to read into it too much. He probably thought just sitting there staring at him talk would be boring, and he didn't think music would make it better, so he went for some different edits. I recommend using Sebastian Lague's style. He's basically seamless with his edits.
@@puppergump4117 Nah, I don't mean to say all of this was driven by some grand intent or deliberate contrivance. I'm just talking about the ultimate effect, regardless of its genesis.
“there is no black magic.” 😍
Yep, you can guess who was my inspiration for this 😉
There is no black magic but there is certainly _bad naming_ , as std::move doesn't move std::forward doesn't forward, as Scott Meyers has said...
Yeah, the naming could be done better. But then naming things is hard. To it's credit, std::move was a good enough name to make all the people involved in it's design and implementation understand each other. 🤷♂️
Great explanation! I highly recommend the book Effective Modern C++ by Scott Meyers for a deep understanding of how it works under the hood.
Yes, I second this recommendation. It is a bit advanced though but great nevertheless.
This was a great video! Thanks man, I subscribed
Thanks 🙏 happy that you liked it!
Great video once again.
I have one small addition: when declaring a move constructor and a move assignment operator it is a good idea to mark is as `noexcept`. Most of the time this should be possible because you're just moving some pointers around. I think main the reason for this is the MSVC compiler which (for some odd reason) can still call the copy constructor even when you do a std::move if the move constructor is not marked as noexcept.
Generally, it is not required but in most cases it is possible and it is a good coding practice.
A good follow up episode could be perfect forwarding, which is basically extending value semantics to generic types. The problem is that in order to understand perfect forwarding one must first understand templates, which is a huge topic on it own.
Thanks Zamfir! I did not know about the MSVC quirk! 😳 Do you have a link for me to read more about it? That sounds really interesting!
As for noexcept I did not include it because we did not talk about exceptions yet 🤷♂️ And you're right about perfect forwarding: we need templates for that. It's coming but later 😬
Great lesson, it really helped me! + bonus points for cool card tricks
Glad I could help!
Thanks a lot! Do you have any plan on touching on forwarding/universal references? How much do you use them on your daily work? I sort of feel it's mostly used by library developers
Edit: Oh I read the comments; you do. :)
That is a great question! I will cover forwarding later as I believe it is a much more advanced thing. And yes, I would agree that this is mostly for library development. I did use it a bunch of times but rarely in production code to be honest 😬
Wish I saw this video a week ago, I was having trouble with objects disappearing from vectors after they move in memory. The solution for me was to use a copy constructor, because the constructor isn't called when the object is copied.
You can blame my laziness if you like. 😉 We can always agree that I should release these things quicker 😬
I always thought of rvalues as temporary values, which are destroyed as soon as they go out of scope. This is why you can "scavange" its resources. But unfortunately how c++ works is that rvalues can bind to lvalues, basically violating that assumption. Its making a possibly short lived object live longer than it actually is. This will cause them to become possibly dangling references!
Sorry, I’m a bit lost, could you maybe give an example of what you mean?
@@CodeForYourself With current c++ rules, this would be allowed to compile:
auto even = std::vector {1,2,3} | std::views::filter([](int i) { return i%2 == 0; });
ranges are evaluated lazily, thus the ranges filter will hold an lvalue reference to the vector. with current c++ rules this would be allowed as rvalues can be extended to lvalues.
But compilers are smart and this does not compile since the filter will hold a dangling reference.
Lets talk about temporary lifetime extension. I can use temporary lifetime extension to store either a value or a reference in a reference
So thanks to temporary lifetime extension i can this:
A const& a = A();
or this:
A const& a = get_A(); // where get_A( ) returns a reference.
this will hold rvalues just fine as well as regular values trough Temporary lifetime extension.
(In a way auto const& is a universal storage for any value or reference.)
Consider a getter for some struct A, that returns a const A&&, so can steal its contents if needed. Or use std::move.
const A& a = std::min(a1.getA(), a2.getA()); (assume As are comparable)
The input of std::min are 2 rvalues references and due to the implementation of std::min they accepted as lvalues and thus get returned into lvalue references, and will also be returned as lvalue reference, as far as temporary lifetime extension is concerned there is nothing to extend the lifetime of.
This means a dangles.
Maybe we can write our own min using universal references and forward them out of min. And if both of them are rvalue references we can still steal their resources after min if needed.
but the problem is a still dangles. you can assign an rvalue reference to an lvalue reference but since its actually an rvalue reference it gets destroyed and you get the dangling reference.
Consider a getter for some struct A, that either returns a an A, a const A&. (Or even a A const&&)
Ideally in some generic code i want to hold either the value or the reference.
decltype(auto) foo() {
const A& a = get_A();
}
if get_A() returns a value, we get a dangeling reference due to it being an rvalue.
and using
auto foo() {
const A& a = get_A();
}
this will always copy, because auto is always a value.
This is a problem due to Temporary lifetime extension lying about its value. As far as the type system is concerned get_A() always returns a reference. When in fact it could be a value.
This could actually be solved by making it illegal to bind rvalue references to lvalue references.
You have a very friendly face 🌞. I imagine it would be difficult for you to intimidate someone or to scold children
@@akshaypratap4123 that’s not how I think I am usually perceived at all. Especially when I’m coding. 😅
That was a really nice cake.
Yes! My mom made it for my PhD defense 😄
@@CodeForYourself Such a perfect PhD cake. And I guess it is actual unfolded pages from your thesis.
Yep, exactly. I still have no idea how she pulled it off!
i wish the compiler didnt allow us to use the object after reference move operation
Yeah, it would be cool. But there is a reason why it is done the way it's done. We would have to record the fact that the data was moved from somewhere 🤷♂️
The main problem is when you move data conditionally.
if(something) a = std::move(b);
After this point you may or you may not use `b` and the compiler cannot help you because the decision happens at runtime.
But I agree that in cases where the compiler *can* prove that the object is moved-from then it should print a warning or something.
Actually, you can still use a moved-from object but there are only 2 allowed operations: destruction and assignment. Meaning you can safely destroy the "empty" object or you can give it a new meaningful value by assigning to it.
Clang-tidy bugprone-use-after-move can help sometimes
@@CodeForYourself Not a good idea, how would you swap two variables? Currently, it is legal to access an object after it is moved from, it should be in valid, but unspecified state. That means, it could be assigned to, and all const member functions could be called on it (because they have no pre-condition on whether they could be called or not). Many standard library types are in valid and in SPECIFIED state after being moved from, typically in an empty state. Consider following example.
int main()
{
huge_type a;
huge_type b;
huge_type tmp;
a = make_something_huge(1);
b = make_something_huge(2);
/* swap begin */
tmp = std::move(a);
a = std::move(b); /* oh no, use-after-move of variable a according to your rules */
b = std::move(tmp); /* oh no, use-after-move of variable b according to your rules */
/* swap end */
assert(tmp.is_empty()); /* oh no, use-after-move of variable tmp according to your rules */
}
Remember, assignment operator is just syntactic sugar for a member function. You can imagine the previous example using a.assign(std::move(b)) instead.
What would be better is destructive move. After an object is moved-from it is destroyed (its destructor is being run) and although its name is still in scope, referring to it is a compiler error. Example:
int main()
{
huge_type a;
huge_type b;
a = make_something_huge(1);
b = make_something_huge(2);
a = std::destructive_move(b);
b = make_something_huge(3); /* oh no, compiler error, b was already destroyed, according to my rules */
}
And I wish it didn't let me check if an unsigned int is less than 0, but good practices prevent these problems.
Constructive criticism. I'm not sure if it's to mask a blend + jump cut, but I would recommend not doing that zoom things that's become so popular lately. I can tell it's not your intention, but I find it oddly and indeed, shamelessly domineering yet sneaky. It's just really kind of gross even if it's meant for emphasis. Though my brain is processing this video as a flat image with inferred depth (your head becomes larger), fact is, IRL if that was happening it would mean one of us suddenly leaned in closer to the other's face, then back, then lean, then back. It's weird. Don't know who started it, but I dislike it. A lot. When weird and mediocre is the norm, different may well be simply better. Be different. Resist decay.
I see the point you're making here and mostly agree with it. The amount of zooming in is over excessive in this video. I will probably settle somewhere in the middle in future. I still like how zoom works for important moments but will try not to use indiscriminately.
@@CodeForYourself I think, if we try to put it in practical and mechanical terms, a primary issue is in how activation of threat processing interacts with working memory and other short term memory state. A lot of research shows, regardless of context, there is a hard bias towards interpreting any object approaching as potentially threatening. The unpredictability and suddenness of the zoom acts as a neurological "interrupt" signal, which eill either rapidly store existing state in a poorly structured form, or will dump existing state.
It's not as extreme as suddenly registering that you're looking into the face of a lion or something through the tall grass, but the underlying fundamentals are there. Personally I think high contrast works better for emphasis than the startle response. So information is easily organized into a tree-like dendritic structure, which naturally fits into the associative web-like structure of long term storage.
I think you're trying to read into it too much. He probably thought just sitting there staring at him talk would be boring, and he didn't think music would make it better, so he went for some different edits.
I recommend using Sebastian Lague's style. He's basically seamless with his edits.
@@puppergump4117 Nah, I don't mean to say all of this was driven by some grand intent or deliberate contrivance. I'm just talking about the ultimate effect, regardless of its genesis.
@@Acetyl53 I didn't notice it though. But luckily, you don't have to watch the videos to get all of the information if it's a problem.
How many tries did the card shuffle take? 🤔 😆
I'll just say many. But I learned how to do it long time ago. Long enough to forget already 😬