you do realize that if you hire Jason to come onsite to teach you C++ that you have to std::move() him, and he will stay there until he goes out of scope.
Depending on your compller, you may be able to detect incorrect usage of std::move using compiler flags. For example gcc's Wpessimizing-move and Wredundant-move. For everything else I hear Jason Turner is available for onsite code review and training 😉
The thing is, the bad moves in this example aren't redundant or pessimizing moves, they're perfectly legitimate uses of move which no sane compiler would warn on. You could just do better by not creating the objects which need to be moved in the first place.
@@GreenJalapenjo it's a legitimate use of a std::move, only because he made the mistake to define a move constructor and the destructor at all. That example is misleading and probably harmful for people who don't know how the compilation and optimizing actually works. Jason explicitly told the compiler, that there is no way to optimize the calls to the destructor and the move constructor at all. He basically told it to print something, when an object leaves lifetime or transfers ownership.
@@Febbe1 It's a dummy class which only exists to print what's going on, for illustration purposes. Imagine it was a std::vector or std::string instead. Moves would be more efficient than a copy, but not as efficient as using NRVO to construct the values in the right location in the first place.
The key takeaway: std::move will not solve the "extra copies" problem magically. It is about transferring resource ownership and NOT doing copy elision. Just use proper semantics... And yes, as a particular application of this principle is this particular use-case where std::move was used to attempt to resolve a problem it was not designed to resolve...
@vishaltripathy3620 that's one of the most bullshit comments under this video. And probably that clickbait headline is the cause. But moving an object is always faster than coping an object. Both for rust and cpp. This video just teaches that a std::move can prevent optimisations like copy elision.
@@Febbe1 I shouldn't really respond to comments written in this manner but here you go, I'm responding to that bulllshit you just wrote. > But moving an object is always faster than coping an object No. It is not *always* waster. One simple example: if an object is fully allocated on the current stack, there is no way you can move that object "faster" than copying.
@@KennyMinigun Your example does not violate my statement, since an object, which recursively has automatic storage duration, is not "movable" and a universal quantification over an empty set is always true. You could argue, that one could implement a custom move constructor to trick the compiler, but that is negligible since this is a bad practice anyway. My problem with the original statement of @vishaltripathy3620 is, that he/she compared 2 completely distinct incomparable topics (rust vs c++) to justify a wrong statement (do not use std::move).
I agree with the folks that are telling you to chill with the hiring calls. It comes off desperate and condescending when you keep repeating…and…keep…repeating…it…slowly. Being condescending doesn’t make people want to hire or be around you. TH-cam is about making content for sponsor/ad/YTpremium revenue. If you don’t think the content is worth that exchange, just don’t make it. Don’t make it and then condescend people. Based on my experience, most people in this TH-cam community think information should be free and maybe apps/services should be paid. With courses and on-site training, you are gating information which is always going to be a hard sell.
@@pmcgee003 So being 400+ episodes in gives you a free pass to be condescending to people? Who wants to pay to be talked to like that? It’s obvious he’s having trouble landing clients or he wouldn’t sound so bitter towards his audience.
@@roblayton3190 after 400+ episodes of free content, I'm pretty sure that he is 100% justified in wanting to actually make some money out of his content, no? he offers this same information with his on site training, that's what his job is all about, but instead, rather than selling it, he has decided to give part of his knowledge out for free. What is there to complain about? that he's asking for some breadcrumbs? holy shit.
I think this video is an argument against "named temporaries", not `std::move`. I completely agree with avoiding named temporaries for exactly the reasons described, but there are many situations where `std::move` is useful and correct. The statement "`std::move` is a code smell" is so free of context that it is at best meaningless and at worst misleading.
exactly! But Jason probably sees many beginners using move to "optimize" temporaries. Better title: "How (and why) to avoid named temporaries and not use std::move with them and why you should hire me for onsite training if you ever see someone at your company doing it"
It is also an argument to avoid custom special member functions instead of defaulting / not declaring them at all. Actually, the video is misleading. Moving a truly movable object is at least as fast as copying it. The example in the video only works to demonstrate this wrong statement, **because** the compiler is allowed to copy elide. When that optimisation is disabled, the hole house of cards breaks. Then it's a decision which code path the compiler has to do. It will always print something.
Yep, exactly. Jason is mixing up two different problems. In his example, he just could construct the object directly with the value instead of a temporary object. For complex construction with several steps, it is always better to move this to a separate function.
Yes, that assessment is fair, about named temporaries. But I still say all uses of `std::move` should be examined, which puts them in the category of "code smell."
Regarding return value optimisation, in the case of Windows on x64, MS went further and it’s not just a compiler *optimisation*. It’s actually part of the ABI that any compliant compiler is *required* to implement. If the return value is more than 8 bytes, the caller must pass the address to sufficient storage in RCX.
5:25 It is safe to call delete on a pointer with NULL or nullptr as a value. Interestingly enough, in the new C standard, calling free on NULL became UB. EDIT: Well, the C committee changed their mind (thank god). C23 is still not finished btw.
free(NULL) is UB now???? nooooooooooooooo I mean, I don't think anyone is actually gonna change their behavior (especially because of dynamic linking and such, code written before C23 would still be affected), but that's SUCH A USEFUL FEATURE
The title should be "Don't use move when copy elision is available". And that I cannot agree more with: copy elision is a kind of compile time move and that's great. But code that implements strong ownership semantics has to use std::move extensively. Are you suggesting that ownership modeling should be avoided? I would bet that no, but how do you do that without std::move?
The lambda solution is very nice, but in this specific example ading explicit Lifetime(int);(and removing the default ctor unless there's some compelling reason to have it) to the class definition would have the same effect and should be the idiomatic way to do it imo.
Hopefully that is obvious to everyone but I think it is hard to come up with trivial examples that fit nicely on the screen of a youtube video/slides to illustrate a point like this.
Thank you for putting those pieces together! I knew about each of them separately, but never really grocked why you (and others) considered std::move a code smell. That was brilliant!
With this method you also don't have to worry about the moved-from stack variables l1 or l2 being, in a practical sense, invalid after the point of the move.
Great video, I had recently a similar issue with type erasure, where I then understood the emplace method of std::any. I guess this goes into the same direction. Cheers
The place where I see std::move as inevitable is when you're returning a transformed input argument from a function. By transformed I mean that you either return the input argument itself or an object constructed from this input argument or a member of the input argument.
Yep, we should definitely hire Jason for some internal training about what RVO is! Appeared in 1998, truly discovered in 2023 😄 I'm usually a big fan of Jason content and talks, very enlightening, but this short one is...well... I'm actually surprised that someone with that pedigree doesn't build an explanation based on the differences between using RVO the right way on the one hand (as shown in that example), and avoid writing move constructors or assignment operators when it's pointless (trivial / non-movable types) on the other. If the point is to show that a lot of us are implementing move ctor/operator= when we shouldn't, the rationale behind it doesn't feel right, as RVO applies to any type, movable or non-movable.
Hmm... I didn't want *writing your own move operations* to come up AT ALL in this conversation. I am a very strong adherent to the Rule of 0, unless I really really need to. I truly wanted this episode to focus on "don't name things if you plan on passing them (via std::move) somewhere else" Use functions to generate values and let the compiler do the optimal thing.
-Whenever you have a range of values and you want to put them in another range, and donʼt care about the values in the old one, and there isnʼt a more specific alternative like splice available- wrong std::move
Only when "sinking" an object. Such as a constructor. But you have to make sure you use it correctly. Almost all use cases of `std::move` I see in the wild are incorrect. They either * don't use move when required to initialize a subobject or * Move something that is ultimately umoveable
I know it's just an example, but in the case shown I would rather just build an (explicit) constructor that I then call instead of this external helper function, like so: `std::array a{Lifetime(42), Lifetime(43)};`, or would I?
I made quick benchmarks for both approaches on some compilers (with and without memory allocate/free in Lifetime object). std::move() slightly (~15%) better in both cases. As a rule of thumb I'd recommend benchmarking if performance matters when using lambdas/std::function/etc. Those usially have performance hit.
@@BryceDixonDev Unfortunately I didn't expect that code would have any value for anybody and already deleted it. Actually it's a trivial task to do such benchmark using Google Benchmark library.
@@embracevoidGiven that your post seems oddly fixated on the lambda which was an implementation detail in Jason's example, I wonder if you introduced some pessimization in your benchmark, like using std::function when you didn't need it. From other videos, you'll see Jason recommend making lambda's static constexpr when not capturing, or if the goal is to disable ADL with a lambda, it would be instantiated globally, so there shouldnt be any overhead in a more production ready version of this example. And even constructing the lambda on the stack shouldn't be that bad as it has no captures. I'll have to try it myself, but your results seem suspicious.
@@oracleoftroy these were my thoughts, which is why I wanted to see the source. Just sharing results without context for how they are achieved is misleading at best.
Probably it could matter if destructor will do something bigger instead of just writing to console, but I'm not ready to try to check it with benchmark
Great video! I was interested in how this approach worked for different containers. I found that std::vector requires a default and move constructor as opposed to std::array. In a similar way, std::pair also requires a default and a move constructor but a struct containing two elements just requires two default constructors. It’s not obvious to me why there would be a difference in how they’re constructed?
My naive understanding is that, since an std::vector can be resized, this leads object to being moved in memory, which means that new objects are created at the new location and the older objects are moved in there (since C++ is not C# and objects cannot be moved in memory while preserving their identity). Arrays, on the other hand, have a fixed sized and objects never move. The requirements are thus very simple,
@@cppweekly This is the specific example I tried in godbolt. The output that I get says that the initialiser list for vector results in two default and two copy constructors. Using emplace_back (or push_back) results in two default and two move constructors. The default and move constructor is also the case for std::pair but not the simple struct. #include #include #include #include #include struct Lifetime { Lifetime() { std::puts("Lifetime() // default ctor"); } ~Lifetime() { std::puts("~Lifetime() // destructor"); } Lifetime(const Lifetime &) { std::puts("Lifetime(const Lifetime &) // copy ctor"); } Lifetime(Lifetime &&) { std::puts("Lifetime(Lifetime &&) // move ctor"); } Lifetime &operator=(Lifetime &&) { std::puts("operator=(Lifetime &&) // move assign"); return *this; } Lifetime &operator=(const Lifetime &) { std::puts("operator=(const Lifetime &) // copy assign"); return *this; } int member_data; }; template struct LifetimeStruct { T1 first; T2 second; }; auto make_lifetime(const int value){ Lifetime l; l.member_data = value; return l; }; int main(){ std::puts("Array"); auto array_value = std::array{make_lifetime(12.0),make_lifetime(122.0)}; std::puts("Vector"); auto vector_value = std::vector{make_lifetime(12.0),make_lifetime(122.0)}; std::puts("Vector emplace"); std::vector vector_value_2; vector_value_2.reserve(2); vector_value_2.emplace_back(make_lifetime(12.0)); vector_value_2.emplace_back(make_lifetime(122.0)); std::puts("std::pair"); auto pair_value = std::pair("first",make_lifetime(12.0)); std::puts("std::make_pair"); auto make_pair_value = std::make_pair("first",make_lifetime(12.0)); std::puts("LifetimeStruct"); auto struct_value = LifetimeStruct{"first", make_lifetime(12.0)}; return 0; }
You do not want to avoid move. If you want to shallow copy of an ephemeral object, you move it, simple as can be. If you want a deep copy an object, you do not move it. If for some reason you are worried about constructors or destructors being called, then you write those so they are not error prone and actually behave in a performant non contrived way. Or, you could rely on some magic sauce code that apparently is a life saver...
Good episode, but you can not always avoid std::move() ... so it would be good to explain the cases where you have to use std::move, and you can avoid using it.
Say you had a situation like the following: class My class { MyClass(std::string s) : m_s{Std::move(s)} {} std::string m_s; }; void foo() { std::string value = // get some value // insert some code that uses value as a reference MyClass m{std::move(value)}; } Obviously, we only have one actual allocation for the string, but 3 (?) destructor calls. Is there a way this could be designed better to avoid all the destructor calls where you need to reference some data before it's moved into its final ownership? Unfortunately I don't have a company to hire you at ;) thanks for all you do
Actually you have 2 allocations, because you're passing the value string by value. Unfortunately we can't really get rid of the calls to the dtors of moved from objects since there's no destructive move in c++, but they are basically noops.
@@TsvetanDimitrov1976 is it 2 allocations? Yes the signature of the constructor takes by value but when it's actually called the string is moved, so I was under the impression that the parameter is move constructed and doesn't allocate
@@TsvetanDimitrov1976 I think it's only 1 allocation of the innards of the std::string because the buffer is gutted from the one on foo's stack and moved into MyClass's constructor and then moved into MyClass's member variable in the initializer list. I don't have a compiler at hand, but I'm pretty sure that's right.
it depends. If you passed an rvalue string then it would be only 1 allocation. However, if you had an existing string object and you pass it to the ctor of this class, then it would create a copy, thus you now have 2 strings.@@youtubeviewer7077 the ideal way to deal with it is a lot of ugly glue code like that: class MyClass { public: MyClass( ) = default; // or MyClass( ) = delete; if you don't need a default ctor MyClass(const std::string& str) : m_str(str) { } // copy ctor MyClass(std::string&& str) noexcept : // noexcept is important for use in containers m_str(std::move(str) { } // move ctor template requires std::is_constructible_v MyClass(Args&&... args) noexcept(std::is_nothrow_constructible_v) : m_str(std::forward(args)...) { } /* /|\ | this is an optional in-place constructor that will construct a new string using the arguments passed to the constructor. Note that since it has variadic arguments it WILL SHADOW regular copy/move ctors. But in this case it would act as a copy, or a move, or an in-place ctor depending on the passed arguments */ private: std::string m_str; }; Is is ugly af, but that's the most effective way to deal with the object construction. I hope for static reflection and code injection to be included in c++26 to abstract away a lot of glue code like that
I know this behavior long time ago. So i always dare to return stack created object, since it is still fast enough. Handling rvalue reference sometimes hurt productivity.
Jason, what are your thoughts on using std::move in constructors for initializing member variables? I like to do this because it gives the user of the class a choice as to whether they want to incur a potentially expensive copy or not. So, for example, instead of MyClass::MyClass(const std::string& str) : m_str{str} {} I always write MyClass::MyClass(std::string str) : m_str{std::move(str)} {} That way whenever the class is used, we can choose whether we want to copy some data into the class, or move it into the class.
Does not seem worthwhile. In the 2nd case, the copy constructor will always be called before using std::move. In the 1st case, the copy constructor is called in populating m_str. So the 1st case saves an extra move constructor. You cannot avoid the copy. And sometimes all have done is use an extra move constructor.
@@stephenhowe4107 It's not true that the copy constructor will always be called in the second case. If the caller passes in an r-value reference it will invoke the move constructor to create the str temporary. std::string myStr = "Hello, World!"; MyClass myClass{std::move(myStr)}; This results in two moves and zero copies. If I had implemented the MyClass constructor the first way, it would require a copy no matter what.
but you still need std::move for implementing your own move ctors\assignment operators and glue code like that exists in every project that's somewhat nontrivial. C++ is known for solving nontrivial tasks, thus you still need know how and where to use std::move.
Note from Jason's assistant - I read and summarize the comments for him, so he does get to ignore a few of them without completely missing the important things. :)
Randomly recommended this by youtube and two minutes in you've done NOTHING apart from plug your products in the most passive aggressive way possible. Will be adding to my ignore list.
Personally, I consider that a very different case than what's been shown here. The initial intent of using std::move() here, was to avoid unnecessary copying, and in cases where the objects are expensive to copy, it still has a benefit. With unique_ptr, what is happening is transfer of ownership as it is not a copyable object, rather than avoiding unnecessary copy; the std::move() is in it's true spirit aiding in transfer of ownership of a resource from one object to another and keeping the semantic of that class intact. So, I wouldn't see it as a code smell or a negative.
I really do hope that you are moving the unique_ptr because you really are transferring ownership. It is perfectly fine to pass the raw pointer from unique_ptr::get(), or a reference to the value (after a proper nullptr check) to a function as a parameter. Just want to make that clear to anyone reading because sometimes people get confused and think that if they are using smart pointers, they have to pass the smart pointer object itself everywhere.
I love your videos Jason, but this isn't a case where you shouldn't use move, but an example where you don't need at all move. This has nothing to do with the cases where move should be used ! And you don't need a lambda at all, all you need is a proper constructor or a constructor delegate.
Hello, fellow C++ enthusiast, please learn to do this instead: struct LifetimeWriter { int value{ 0 }; LifetimeWriter( int value) :value(value) { std::cout
It's partially RVO, but I don't like the name "RVO" (return value optimization) because it implies that compiler has actually "optimized" something. 99% of the time it falls out from how calling conventions work.
@@Omnifarious0 I agree and i tend to find that well thought out code won't call for the use anyway. But as far as the point of the video goes, Jason is less talking about the actual call to move(or the cast to r-value&) and more about the very use of move constructors on objects with involved destruction
@@Omnifarious0 I'd say that in cases of transferring ownership, std::move() makes total sense. Semantically, I always saw std::move() as a way of ownership transfer, more than "here's this function for optimization" as many programmers seem to see it for.
Those are effectively the same thing, but the point being that you should arrange your code so that move is not a question. Don't name things you plan on giving to another function.
You’ve essentially just forwarded their construction, like if you used emplace({42}). Wrapping it and a lambda doesn’t change anything here? Am I missing something
The key is to delay construction until you have enough information that you can construct the object with a meaningful value. This is also why "const" is a good default - you must wait to initialize an object until you can initialize it with a meaningful value. Many of the same code quality improvements result.
Yeah okay, I can see how this would be more useful if the class didn't expose certain things in a constructor and you would otherwise need the object instance to start setting values, I think I was taking your example a little too literal and wasn't seeing the bigger picture.@@cppweekly
That means, that if you used the destructor for book keeping (some performance or correctness metric for instance) and assumed that move really transferred ownership, you'd be in for a surprise..? Would it be complicated to explain what happens from a memory layout perspective there? Destructors being caled at the end of a scope or stack frame, would there then be a relocation? Or maybe if you use a local variable in a local thread and then want to write selcted elements into a data structure owned by the main thread. Could you then std::move? Does it make a difference if it came from a data structure like a vector?
Move does exactly what the implementer asked it to do. But on a different note - if you're using your destructor for bookkeeping, like performance tracking, you are DRASTICALLY affecting the performance of your system. If your object could otherwise be trivially destructable, then it becomes unknowable how many of the objects were destroyed.
The "problem" in C++ is that move is not destructive, meaning you still can access to the variable after a move (which, in fact, is UB). And that's because compiler doesn't have borrow checker (like Rust have for example), so the choice of the move design is coherent. If the C++ had the destructive move design, the destructor would not be called on an instance that has been moved, and we won't avoid using move for any kind of objects. Now, knowing that, using move is not a big deal. We just have to remember to nullify the "other" object during a move to not end up with a bad situation when the destructor is called. Sure the optimisation of building the instance in the exact spot is better is it calls exactly 1 ctor and 1 dtor, but move can still be useful in a lot of case ;)
It is most definitely *not* undefined behavior to access a moved from variable. It is unspecified but valid. The standard can't specify exactly what a moved from type must do because it doesn't know your codebase, but it does specify what the standard types do. E.g. a moved from unique_ptr is like it was set to nullptr, or a moved from vector is like it is empty. The absolute minimum is that it must be possible to destruct the object. Any undefined behavior in the code isn't from the move, it's from treating the moved from object as if it still had its data without doing the proper bounds checks.
@@oracleoftroy You are right but it's not garanteed that the moved value stays in a valid/reusable state. For example, it's fine to just set a boolean to indicate to the destructor if a value is moved or not (and by fine, I just mean it works, not that we should do it !). So it is potential undefined behavior not by the compilers implementation but by how libraries work around this.
@@iamgly I do find that designing movable types tends to push for some sort of 'zero' state, even if normally I would prefer to discourage a 'default construct then assign' pattern. But I can't think of any sort of reasonable move constructor/assignment operator implementation that wouldn't allow at minimum reassignment after move. You need it for std::swap to work. A bool flag can be fine if there isn't a more natural zero state, but you usually want move because you hold a pointer and are avoiding a deep copy, so usually there is something you could null instead. But move only types that aren't pointers can be great for some things (sockets, opengl wrappers), and not all of them have a natural default state value. A swap based implementation can also be fine and avoids the extra bool member. As the moved from value is typically expiring soon anyway, there isn't a problem keeping the old values alive a bit longer and letting the destruction do the real cleanup. Such an implementation should obviously have no undefined behavior unless something else is seriously borked. In my experience, it really isn't hard writing a move constructor/assignment operator that behaves properly without the gotchas you are imagining. It takes a bit of getting used to, but really isn't too different from any other resource management concern than normal copying and RAII deal with. Pro Rust arguments would be stronger if they showed more awareness and less fearmongering about the actual state of C++.
@@oracleoftroy Unfortunately, a Rustacean's arguments can never be stronger because if they actually knew C++ instead of spreading FUD about the language then they wouldn't bother using Rust as idiomatic C++ is equally as safe as Rust, faster to compile and execute, and allows for doing "unsafe" things without a stupid keyword.
@@anon_y_mousse I don't know if I would go quite that far. I think there is real value in the borrow checker. I'm not sure it is quite as high as Rust claims, but it isn't zero and I look forward to C++ borrowing the best parts of the borrow checker on future standards and implementations. But that said, I rarely, though not never, have the sort of issues Rust seeks to protect against. The last time I ran into it, I was doing low level code for an emulator, the sort of code I think would need to be marked unsafe in Rust anyway. I was also writing the sort of code I knew wouldn't have lifetime issues, but I've tried to do in Rust and couldn't appease the borrow checker for the life of me. Often the examples of code Rust rejects but C++ accepts are the sort that would never pass review, but to Rust's credit, Rust finds it and C++ doesn't, and that has value. I think for truly secure code, you still want static analyzers, sanitizers, fuzzers, and all the other sorts of tooling we have in C++ to help ensure correctness, even in Rust. A perfectly accurate borrow checker is probably a halting problem difficult undertaking, so Rust favors false positives over missing real issues. I'm not sure Rust's overly strict borrow checker is the right answer. Maybe a relaxed version that never has false positives but needs supplemental sanitizers, etc. to detect other issues would be better, especially since we probably still need them anyway.
When I was writing in Qt I noticed that they have an object.swap(other object) functions in their framework. Since then I always thought of move == swap and it kind-of behaves like this. I think maybe name swap would be better.
iirc, std::move takes the object you pass it and returns an r value allowing you to use move semantics on it. I don't think you're actually swapping anything.
Ok, wait wth. No. This is why people use move. So the destructor is NOT called and they can move the object. They DONT want to construct a new one. The destructor is called when the object falls out of scope. So, yes, it's called 4 times so there is this reference count in action (the mechanism of which I have no idea) but this seems like it's doing exactly what we want. Right?
I don't understand your reasoning. "moving" requires that there is more than 1 object in existence. It's *always* better to have fewer objects when you can. Fewer constructors, fewer destructors, less instruction cache pressure, less register allocation pressure... This is *always* a performance win. The point of the video is how to get that performance win by avoiding both copies and moves and relying on copy and move elision, and directly constructing values in place.
Bjarne tends to agree with my Best Practices. He even regularly recommends my Commodore 64 talk to people. (I have people come up to me at conferences and tell me this.) I'm not worried about Bjarne seeing this episode.
The paranoia of this man to sacrifice code complexity and readability to avoid calling delete on null pointers... This paints a terrible example of writing code in general. There is nothing wrong with std::move. And what if I want to get multiple types of data and do some operations with them before pushing them in a container? Is it really better to make a lambda for every data type?
You appear to have missed the point of this episode - if you structure your code in such a way that you don't need or want std::move, then you will get more efficient, composable, and cleaner code in general.
you should use std::format\std::print(std::println) instead (or fmt library if you don't have c++23 support yet). Iostreams have a hidden cost of synchronizing with the c stdio library; plus library has better formatting syntax and is type safe
@@Raspredval1337 I'm not worried about runtime cost in this case - I'm just trying to generate cleaner assembly that's easier to read. `puts` is effectively "free" when it comes to the assembly output.
Alright, call me a d-bag, because I am but here is my clapback: Does anyone hire you (OR ANYONE) for on-site training? I've worked at AMD and a company of 5 people and never had on-site training except for a specific product we bought (Jenkins). Employers want to test senior applicants on this sort of material in the interview and MAYBE have them teach the junior and mid devs. Or just have them pick it up through code comments and code reviews, not through specifically scheduling training between the two. After that, I didn't fully follow the video. Your example, just before the lambda was making 4 instances of the object, explicitly. Then, you made the lambda and had the array members filled out with that. As you know, this is quite different from the code just before that. I see you're sort of showing that you can initialize in place and add to the array all at once and you did explicitly state how the compiler is taking care of single moves (without using implicit moves or anything since yours are explicit) but Im otherwise not connecting these behaviors before and after the refactor. They're very different to me. Ok after rubber ducking it a bit, writing the above statement, I think I'm following what you're exemplifying better but still am lacking to come to the same conclusion you are. Either way, I appreciate the video. I understand your frustration a bit since your goal is to get hired for on site training but I feel the responses you got that brought you to make this video could be easily foreseen. Edit: Ok after I walked away and thought a moment, I realized (afaik) people think that the std::move somehow totally would save a copy/construction operation and you were showing it does not. I guess the reason I didn't get this is because I am aware that the code would behave just as you showed it would.
Yes, many companies hire me and many other trainers for on-site training. This is actually a very normal thing. I suggest you look around for different companies that care about investing into the education of their developers.
You need to chill on the hiring calls, it's a bit desperate sounding IMO honestly sorry. Interesting thought though that I hadn't considered, using RVO to avoid any copies or moves entirely.
If C++ didn't allow the use of moved values this wouldn't be a question. But misleading, as the example is just for a "factory" pattern, there are plenty of examples where you should move.
Compared to what? Serious question. - Visual Studio is probably still the most functional C++ IDE, IMHO, but it's still not fully cross-platform, and much of my work is done on Linux. However, JetBrains has its ReSharper products that enhance it even further. - Eclipse is still pretty decent for Java, but not great at anything else, and its design is heavily weighed down by legacy. - I can't think of any IDE that even approaches PyCharm's support for Python. - VSCode is a good programmer-oriented editor, but requires a lot of configuration as a DIY IDE, and I already have Vim/NeoVim as an option. However, VSCode's Vim/NeoVim plugins bring all that power to VSCode, too, which is nice. JetBrains products are very competitive with all of these IDE options, and it has a very good modern foundation in its IDEA platform that no other platform is competing with. VS Code is the closest anyone has come, but it lacks the "batteries included" aspect of JetBrains' language/task-specific IDEs -- but it's free, which is a fair tradeoff.
@@adamkonrad, I prefer Visual Studio Proper and Qt Creator, but VS Code also works extremely well, for an Electron app it's amazing. Jetbrains IDEs, on the other hand, can load even a 32-core CPU to 80% with bullshit background tasks that run for minutes.
@@TsvetanDimitrov1976 "has its problems"... lol. The arrogance... You're not smart enough to handle a million states in your head. Pull your head out of your butt and smell the reality. Many people who are masters in C and C++ have been doing memory bugs, including whoever you consider your mentor. Guaranteed. When you say the borrow checker offers "nothing" in a video that shows how C++ move is broken... well, you have a few more lessons to learn in being humble, Mr. Expert. And btw, I did C++ for 15 years. But I'm not arrogant enough to think that my brain is better than compilers to track tons of states.
@@TsvetanDimitrov1976aren't destructive move less optimized than simple ownership transfer? since you have to flag in some way that the object is now in "destroyed state"
Oh man. I like Jason's talks on CppCon. So was eager to check this channel out. Not quite what I was expecting. I have seen the gist of this topic explained in at least a dozen stackoverflow threads. So why the constant bombardments of on-site training? It's such a clear topic to explain through code example.
I really get the feeling we should hire you for onsight training
... only then will he really answer the question!?
"on-site" is when you get training. "on sight" is when you shoot someone. Still not sure which one you meant, though.
@@PhilHord maybe Jason is the type of nerd who trains on sight?
@@AtomicAndi Yes, it's the educational approach called onslaught training.
@@Evan490BC OH, I never quite heard that correctly I guess. Now I cannot unhear.
you do realize that if you hire Jason to come onsite to teach you C++ that you have to std::move() him, and he will stay there until he goes out of scope.
brilliant one, mate🤣
Depending on your compller, you may be able to detect incorrect usage of std::move using compiler flags. For example gcc's Wpessimizing-move and Wredundant-move. For everything else I hear Jason Turner is available for onsite code review and training 😉
The thing is, the bad moves in this example aren't redundant or pessimizing moves, they're perfectly legitimate uses of move which no sane compiler would warn on. You could just do better by not creating the objects which need to be moved in the first place.
clang also have those flags
@@GreenJalapenjo So for everything else you can hire Jason?
@@GreenJalapenjo it's a legitimate use of a std::move, only because he made the mistake to define a move constructor and the destructor at all. That example is misleading and probably harmful for people who don't know how the compilation and optimizing actually works. Jason explicitly told the compiler, that there is no way to optimize the calls to the destructor and the move constructor at all. He basically told it to print something, when an object leaves lifetime or transfers ownership.
@@Febbe1 It's a dummy class which only exists to print what's going on, for illustration purposes. Imagine it was a std::vector or std::string instead. Moves would be more efficient than a copy, but not as efficient as using NRVO to construct the values in the right location in the first place.
This video is the strongest recession indicator I've seen.
Best of luck Jason!
best of luck everyone I think
I'm selling half price baked beans, link in bio
@@tokyospliff I'm selling full-price half-baked beans. No customers yet...
The key takeaway: std::move will not solve the "extra copies" problem magically. It is about transferring resource ownership and NOT doing copy elision. Just use proper semantics...
And yes, as a particular application of this principle is this particular use-case where std::move was used to attempt to resolve a problem it was not designed to resolve...
c++ is copy by default. rust is move by default. rust is not faster
@@vishaltripathy3620 And what was this take about, exactly?
@vishaltripathy3620 that's one of the most bullshit comments under this video. And probably that clickbait headline is the cause. But moving an object is always faster than coping an object. Both for rust and cpp. This video just teaches that a std::move can prevent optimisations like copy elision.
@@Febbe1 I shouldn't really respond to comments written in this manner but here you go, I'm responding to that bulllshit you just wrote.
> But moving an object is always faster than coping an object
No. It is not *always* waster. One simple example: if an object is fully allocated on the current stack, there is no way you can move that object "faster" than copying.
@@KennyMinigun Your example does not violate my statement, since an object, which recursively has automatic storage duration, is not "movable" and a universal quantification over an empty set is always true. You could argue, that one could implement a custom move constructor to trick the compiler, but that is negligible since this is a bad practice anyway.
My problem with the original statement of @vishaltripathy3620 is, that he/she compared 2 completely distinct incomparable topics (rust vs c++) to justify a wrong statement (do not use std::move).
I agree with the folks that are telling you to chill with the hiring calls. It comes off desperate and condescending when you keep repeating…and…keep…repeating…it…slowly. Being condescending doesn’t make people want to hire or be around you.
TH-cam is about making content for sponsor/ad/YTpremium revenue. If you don’t think the content is worth that exchange, just don’t make it. Don’t make it and then condescend people. Based on my experience, most people in this TH-cam community think information should be free and maybe apps/services should be paid. With courses and on-site training, you are gating information which is always going to be a hard sell.
Jason is 400 episodes in, just for cppw ... I don't think he likely needs advice on how TH-cam should be utilised, or his presentation style.
@@pmcgee003 So being 400+ episodes in gives you a free pass to be condescending to people? Who wants to pay to be talked to like that? It’s obvious he’s having trouble landing clients or he wouldn’t sound so bitter towards his audience.
@@roblayton3190 after 400+ episodes of free content, I'm pretty sure that he is 100% justified in wanting to actually make some money out of his content, no? he offers this same information with his on site training, that's what his job is all about, but instead, rather than selling it, he has decided to give part of his knowledge out for free. What is there to complain about? that he's asking for some breadcrumbs? holy shit.
I think this video is an argument against "named temporaries", not `std::move`. I completely agree with avoiding named temporaries for exactly the reasons described, but there are many situations where `std::move` is useful and correct. The statement "`std::move` is a code smell" is so free of context that it is at best meaningless and at worst misleading.
Yes, I was going to say the same thing. The title of the video should probably be "Avoid using named temporaries" rather than avoid std::move.
exactly!
But Jason probably sees many beginners using move to "optimize" temporaries.
Better title: "How (and why) to avoid named temporaries and not use std::move with them and why you should hire me for onsite training if you ever see someone at your company doing it"
It is also an argument to avoid custom special member functions instead of defaulting / not declaring them at all. Actually, the video is misleading. Moving a truly movable object is at least as fast as copying it. The example in the video only works to demonstrate this wrong statement, **because** the compiler is allowed to copy elide. When that optimisation is disabled, the hole house of cards breaks. Then it's a decision which code path the compiler has to do. It will always print something.
Yep, exactly. Jason is mixing up two different problems. In his example, he just could construct the object directly with the value instead of a temporary object. For complex construction with several steps, it is always better to move this to a separate function.
Yes, that assessment is fair, about named temporaries. But I still say all uses of `std::move` should be examined, which puts them in the category of "code smell."
Ok, first 2 minutes of a 9 minute video is advertisement and then again after the first lines of code…
Dude, I watched half of the video but yet just couldn't skip over your advertisements
Regarding return value optimisation, in the case of Windows on x64, MS went further and it’s not just a compiler *optimisation*. It’s actually part of the ABI that any compliant compiler is *required* to implement. If the return value is more than 8 bytes, the caller must pass the address to sufficient storage in RCX.
It's actually not really an optimization with any ABI going back to about 1998
5:25 It is safe to call delete on a pointer with NULL or nullptr as a value.
Interestingly enough, in the new C standard, calling free on NULL became UB.
EDIT: Well, the C committee changed their mind (thank god). C23 is still not finished btw.
really? why would they do that?
free(NULL) is UB now???? nooooooooooooooo
I mean, I don't think anyone is actually gonna change their behavior (especially because of dynamic linking and such, code written before C23 would still be affected), but that's SUCH A USEFUL FEATURE
Two the two people here, see my edit.
@@kuhluhOG > C23 is still not finished btw
Well that's just a fucking fantastic name then , right? Good job, C committee!
The title should be "Don't use move when copy elision is available". And that I cannot agree more with: copy elision is a kind of compile time move and that's great.
But code that implements strong ownership semantics has to use std::move extensively. Are you suggesting that ownership modeling should be avoided? I would bet that no, but how do you do that without std::move?
The lambda solution is very nice, but in this specific example ading explicit Lifetime(int);(and removing the default ctor unless there's some compelling reason to have it) to the class definition would have the same effect and should be the idiomatic way to do it imo.
Hopefully that is obvious to everyone but I think it is hard to come up with trivial examples that fit nicely on the screen of a youtube video/slides to illustrate a point like this.
@@not_everTrue, the best i can come up with is a class having a non-copiable data member, e.g. std::mutex
the lambda solution is not nice nor the solution. This is the solution:
std::array arr = { LifetimeWriter{5}, {7} };
@@nicholaskomsa1777ok, but what if I need to initialize the array with a for-loop? Now I can't use initializer.
@@dummyaccount1706 Well like the video, you are going to have contrive this and that and why
A little bit too many ads for me.
8 minutes to say implicit move-returns don't need std::move is a lot of time for what accounts to a compiler warning
But I don't have a company and this is my training site. I live in here like The Lawnmower Man.
Thank you for putting those pieces together! I knew about each of them separately, but never really grocked why you (and others) considered std::move a code smell. That was brilliant!
With this method you also don't have to worry about the moved-from stack variables l1 or l2 being, in a practical sense, invalid after the point of the move.
Great video, I had recently a similar issue with type erasure, where I then understood the emplace method of std::any. I guess this goes into the same direction.
Cheers
The place where I see std::move as inevitable is when you're returning a transformed input argument from a function. By transformed I mean that you either return the input argument itself or an object constructed from this input argument or a member of the input argument.
Yep, we should definitely hire Jason for some internal training about what RVO is! Appeared in 1998, truly discovered in 2023 😄
I'm usually a big fan of Jason content and talks, very enlightening, but this short one is...well...
I'm actually surprised that someone with that pedigree doesn't build an explanation based on the differences between using RVO the right way on the one hand (as shown in that example), and avoid writing move constructors or assignment operators when it's pointless (trivial / non-movable types) on the other. If the point is to show that a lot of us are implementing move ctor/operator= when we shouldn't, the rationale behind it doesn't feel right, as RVO applies to any type, movable or non-movable.
Hmm... I didn't want *writing your own move operations* to come up AT ALL in this conversation. I am a very strong adherent to the Rule of 0, unless I really really need to.
I truly wanted this episode to focus on "don't name things if you plan on passing them (via std::move) somewhere else"
Use functions to generate values and let the compiler do the optimal thing.
Awesome as usual!
Happy "Page not found" Episode.
Nice - do you happen to have a video explaining where to use std::move?
-Whenever you have a range of values and you want to put them in another range, and donʼt care about the values in the old one, and there isnʼt a more specific alternative like splice available- wrong std::move
Only when "sinking" an object. Such as a constructor.
But you have to make sure you use it correctly. Almost all use cases of `std::move` I see in the wild are incorrect.
They either
* don't use move when required to initialize a subobject
or
* Move something that is ultimately umoveable
Thanks Jason @cppweekly
A programmer being condescending? I don't believe it!😲
Thanks! That's a very clear explanation.
Good episode! And indeed - bills have to be paid :-)
I haven't looked at the video yet, but if i am creating a dynamic growable array, I may do a few news of a biggee array, moves and delete of rhe old.
Very nice video , thanks Jason! A thorough understanding of object lifetime is really very, very relevant to write clean C++ code
I know it's just an example, but in the case shown I would rather just build an (explicit) constructor that I then call instead of this external helper function, like so: `std::array a{Lifetime(42), Lifetime(43)};`, or would I?
Yes, you probably would. Of course that would sidestep the point Jason tried to make.
I made quick benchmarks for both approaches on some compilers (with and without memory allocate/free in Lifetime object). std::move() slightly (~15%) better in both cases. As a rule of thumb I'd recommend benchmarking if performance matters when using lambdas/std::function/etc. Those usially have performance hit.
Would you mind sharing your benchmark?
@@BryceDixonDev Unfortunately I didn't expect that code would have any value for anybody and already deleted it. Actually it's a trivial task to do such benchmark using Google Benchmark library.
@@embracevoidGiven that your post seems oddly fixated on the lambda which was an implementation detail in Jason's example, I wonder if you introduced some pessimization in your benchmark, like using std::function when you didn't need it.
From other videos, you'll see Jason recommend making lambda's static constexpr when not capturing, or if the goal is to disable ADL with a lambda, it would be instantiated globally, so there shouldnt be any overhead in a more production ready version of this example. And even constructing the lambda on the stack shouldn't be that bad as it has no captures.
I'll have to try it myself, but your results seem suspicious.
@@oracleoftroy these were my thoughts, which is why I wanted to see the source. Just sharing results without context for how they are achieved is misleading at best.
Probably it could matter if destructor will do something bigger instead of just writing to console, but I'm not ready to try to check it with benchmark
It's not condescending if you actually know better, which you usually do :-)
Great video!
I was interested in how this approach worked for different containers. I found that std::vector requires a default and move constructor as opposed to std::array. In a similar way, std::pair also requires a default and a move constructor but a struct containing two elements just requires two default constructors.
It’s not obvious to me why there would be a difference in how they’re constructed?
My naive understanding is that, since an std::vector can be resized, this leads object to being moved in memory, which means that new objects are created at the new location and the older objects are moved in there (since C++ is not C# and objects cannot be moved in memory while preserving their identity). Arrays, on the other hand, have a fixed sized and objects never move. The requirements are thus very simple,
I need some more context for specific examples to discuss.
@@cppweekly
This is the specific example I tried in godbolt. The output that I get says that the initialiser list for vector results in two default and two copy constructors. Using emplace_back (or push_back) results in two default and two move constructors. The default and move constructor is also the case for std::pair but not the simple struct.
#include
#include
#include
#include
#include
struct Lifetime {
Lifetime() { std::puts("Lifetime() // default ctor"); }
~Lifetime() { std::puts("~Lifetime() // destructor"); }
Lifetime(const Lifetime &) {
std::puts("Lifetime(const Lifetime &) // copy ctor");
}
Lifetime(Lifetime &&) { std::puts("Lifetime(Lifetime &&) // move ctor"); }
Lifetime &operator=(Lifetime &&) {
std::puts("operator=(Lifetime &&) // move assign");
return *this;
}
Lifetime &operator=(const Lifetime &) {
std::puts("operator=(const Lifetime &) // copy assign");
return *this;
}
int member_data;
};
template
struct LifetimeStruct {
T1 first;
T2 second;
};
auto make_lifetime(const int value){
Lifetime l;
l.member_data = value;
return l;
};
int main(){
std::puts("Array");
auto array_value = std::array{make_lifetime(12.0),make_lifetime(122.0)};
std::puts("Vector");
auto vector_value = std::vector{make_lifetime(12.0),make_lifetime(122.0)};
std::puts("Vector emplace");
std::vector vector_value_2;
vector_value_2.reserve(2);
vector_value_2.emplace_back(make_lifetime(12.0));
vector_value_2.emplace_back(make_lifetime(122.0));
std::puts("std::pair");
auto pair_value = std::pair("first",make_lifetime(12.0));
std::puts("std::make_pair");
auto make_pair_value = std::make_pair("first",make_lifetime(12.0));
std::puts("LifetimeStruct");
auto struct_value = LifetimeStruct{"first", make_lifetime(12.0)};
return 0;
}
episode not found
You do not want to avoid move. If you want to shallow copy of an ephemeral object, you move it, simple as can be. If you want a deep copy an object, you do not move it. If for some reason you are worried about constructors or destructors being called, then you write those so they are not error prone and actually behave in a performant non contrived way. Or, you could rely on some magic sauce code that apparently is a life saver...
The point of this episode is that you should write your code in such a way as to avoid the need for move.
Good episode, but you can not always avoid std::move() ... so it would be good to explain the cases where you have to use std::move, and you can avoid using it.
Say you had a situation like the following:
class My class
{
MyClass(std::string s) : m_s{Std::move(s)} {}
std::string m_s;
};
void foo() {
std::string value = // get some value
// insert some code that uses value as a reference
MyClass m{std::move(value)};
}
Obviously, we only have one actual allocation for the string, but 3 (?) destructor calls. Is there a way this could be designed better to avoid all the destructor calls where you need to reference some data before it's moved into its final ownership?
Unfortunately I don't have a company to hire you at ;) thanks for all you do
Actually you have 2 allocations, because you're passing the value string by value. Unfortunately we can't really get rid of the calls to the dtors of moved from objects since there's no destructive move in c++, but they are basically noops.
@@TsvetanDimitrov1976 is it 2 allocations? Yes the signature of the constructor takes by value but when it's actually called the string is moved, so I was under the impression that the parameter is move constructed and doesn't allocate
@@TsvetanDimitrov1976 I think it's only 1 allocation of the innards of the std::string because the buffer is gutted from the one on foo's stack and moved into MyClass's constructor and then moved into MyClass's member variable in the initializer list. I don't have a compiler at hand, but I'm pretty sure that's right.
it depends. If you passed an rvalue string then it would be only 1 allocation. However, if you had an existing string object and you pass it to the ctor of this class, then it would create a copy, thus you now have 2 strings.@@youtubeviewer7077
the ideal way to deal with it is a lot of ugly glue code like that:
class MyClass {
public:
MyClass( ) = default; // or MyClass( ) = delete; if you don't need a default ctor
MyClass(const std::string& str) :
m_str(str) { } // copy ctor
MyClass(std::string&& str) noexcept : // noexcept is important for use in containers
m_str(std::move(str) { } // move ctor
template
requires std::is_constructible_v
MyClass(Args&&... args) noexcept(std::is_nothrow_constructible_v) :
m_str(std::forward(args)...) { }
/* /|\
|
this is an optional in-place constructor that will construct a new string using the arguments passed to the constructor. Note that since it has variadic arguments it WILL SHADOW regular copy/move ctors. But in this case it would act as a copy, or a move, or an in-place ctor depending on the passed arguments
*/
private:
std::string m_str;
};
Is is ugly af, but that's the most effective way to deal with the object construction. I hope for static reflection and code injection to be included in c++26 to abstract away a lot of glue code like that
@@youtubeviewer7077 You could always test with Compiler Explorer.
I know this behavior long time ago. So i always dare to return stack created object, since it is still fast enough.
Handling rvalue reference sometimes hurt productivity.
Returning a local stack created object is literally the best possible option. th-cam.com/video/9mWWNYRHAIQ/w-d-xo.html
Jason, what are your thoughts on using std::move in constructors for initializing member variables? I like to do this because it gives the user of the class a choice as to whether they want to incur a potentially expensive copy or not. So, for example, instead of
MyClass::MyClass(const std::string& str) : m_str{str} {}
I always write
MyClass::MyClass(std::string str) : m_str{std::move(str)} {}
That way whenever the class is used, we can choose whether we want to copy some data into the class, or move it into the class.
Does not seem worthwhile. In the 2nd case, the copy constructor will always be called before using std::move. In the 1st case, the copy constructor is called in populating m_str. So the 1st case saves an extra move constructor.
You cannot avoid the copy. And sometimes all have done is use an extra move constructor.
@@stephenhowe4107 It's not true that the copy constructor will always be called in the second case. If the caller passes in an r-value reference it will invoke the move constructor to create the str temporary.
std::string myStr = "Hello, World!";
MyClass myClass{std::move(myStr)};
This results in two moves and zero copies. If I had implemented the MyClass constructor the first way, it would require a copy no matter what.
Yes, that is basically the one place to use move without question.
but you still need std::move for implementing your own move ctors\assignment operators and glue code like that exists in every project that's somewhat nontrivial. C++ is known for solving nontrivial tasks, thus you still need know how and where to use std::move.
Agreed. The title of this video should be "How (and Why) To Write Slideware That Avoids std::move"
What is the _s: prefixing a string literal at 1min25sec, as the argument of puts(), at line 4?
Is it c++?
That's the IDE inserting the name of the parameter to the function. It's not any code that I wrote.
I would hire you to come out to my company. If I didn't just get laid off
Hey Jason, C++ Weekly is great! Really hoping you don't read or at least ignore the stupid comments.
Note from Jason's assistant - I read and summarize the comments for him, so he does get to ignore a few of them without completely missing the important things. :)
excellent video.
I tested the same with std::vector's initializer_list constructor and it makes a copy. Does anyone know why..??
Post a code sample and issue on the github for me to make a followup episode github.com/lefticus/cpp_weekly/issues
Randomly recommended this by youtube and two minutes in you've done NOTHING apart from plug your products in the most passive aggressive way possible. Will be adding to my ignore list.
Oh no ... he might be sweating bullets. Or not.
Is there no avoiding using std::move when passing ownership of a unique_ptr that already exists down into a function?
Personally, I consider that a very different case than what's been shown here. The initial intent of using std::move() here, was to avoid unnecessary copying, and in cases where the objects are expensive to copy, it still has a benefit.
With unique_ptr, what is happening is transfer of ownership as it is not a copyable object, rather than avoiding unnecessary copy; the std::move() is in it's true spirit aiding in transfer of ownership of a resource from one object to another and keeping the semantic of that class intact. So, I wouldn't see it as a code smell or a negative.
I really do hope that you are moving the unique_ptr because you really are transferring ownership. It is perfectly fine to pass the raw pointer from unique_ptr::get(), or a reference to the value (after a proper nullptr check) to a function as a parameter.
Just want to make that clear to anyone reading because sometimes people get confused and think that if they are using smart pointers, they have to pass the smart pointer object itself everywhere.
amazing!
Short and sweet.
Good content.
+1
ads before the start of the video, ads in the video, ads nested inside the video, ads after the video
as someone without a company, I think I should found a company just so that I can bring you in for onsite training
404 - Comment Not Found
Or "Episode not found"?
I love your videos Jason, but this isn't a case where you shouldn't use move, but an example where you don't need at all move. This has nothing to do with the cases where move should be used ! And you don't need a lambda at all, all you need is a proper constructor or a constructor delegate.
Hello, fellow C++ enthusiast, please learn to do this instead:
struct LifetimeWriter {
int value{ 0 };
LifetimeWriter( int value)
:value(value)
{
std::cout
Is that RVO optimization?
It's partially RVO, but I don't like the name "RVO" (return value optimization) because it implies that compiler has actually "optimized" something. 99% of the time it falls out from how calling conventions work.
Condescending? It just made me think. Thanks for the episode!
so it's less about not calling std::move and more about not using the move constructor/assignment for any non trivially destructible object?
If you have to explicitly call ::std::move, you're probably doing something sub-optimally.
@@Omnifarious0 I agree and i tend to find that well thought out code won't call for the use anyway. But as far as the point of the video goes, Jason is less talking about the actual call to move(or the cast to r-value&) and more about the very use of move constructors on objects with involved destruction
@@wigi426 - Yes, I suppose so. This kind of thing is why things like emplace_back even exist.
@@Omnifarious0 I'd say that in cases of transferring ownership, std::move() makes total sense. Semantically, I always saw std::move() as a way of ownership transfer, more than "here's this function for optimization" as many programmers seem to see it for.
Those are effectively the same thing, but the point being that you should arrange your code so that move is not a question. Don't name things you plan on giving to another function.
Telling people they can get more information by hiring you is good advertising. It's not condescending.
You’ve essentially just forwarded their construction, like if you used emplace({42}). Wrapping it and a lambda doesn’t change anything here? Am I missing something
The key is to delay construction until you have enough information that you can construct the object with a meaningful value. This is also why "const" is a good default - you must wait to initialize an object until you can initialize it with a meaningful value.
Many of the same code quality improvements result.
Yeah okay, I can see how this would be more useful if the class didn't expose certain things in a constructor and you would otherwise need the object instance to start setting values, I think I was taking your example a little too literal and wasn't seeing the bigger picture.@@cppweekly
I always avoid std::move because I use languages other than C++
That means, that if you used the destructor for book keeping (some performance or correctness metric for instance) and assumed that move really transferred ownership, you'd be in for a surprise..? Would it be complicated to explain what happens from a memory layout perspective there? Destructors being caled at the end of a scope or stack frame, would there then be a relocation? Or maybe if you use a local variable in a local thread and then want to write selcted elements into a data structure owned by the main thread. Could you then std::move? Does it make a difference if it came from a data structure like a vector?
Move does exactly what the implementer asked it to do.
But on a different note - if you're using your destructor for bookkeeping, like performance tracking, you are DRASTICALLY affecting the performance of your system. If your object could otherwise be trivially destructable, then it becomes unknowable how many of the objects were destroyed.
Accidentally moved from unique_ptr variable, 404 not found
What is the difference between using the lambda and an explicit construtor using an integer?
A named function (or constructor) can be used in place of a lambda with the same effect.
The "problem" in C++ is that move is not destructive, meaning you still can access to the variable after a move (which, in fact, is UB). And that's because compiler doesn't have borrow checker (like Rust have for example), so the choice of the move design is coherent.
If the C++ had the destructive move design, the destructor would not be called on an instance that has been moved, and we won't avoid using move for any kind of objects.
Now, knowing that, using move is not a big deal. We just have to remember to nullify the "other" object during a move to not end up with a bad situation when the destructor is called. Sure the optimisation of building the instance in the exact spot is better is it calls exactly 1 ctor and 1 dtor, but move can still be useful in a lot of case ;)
It is most definitely *not* undefined behavior to access a moved from variable. It is unspecified but valid. The standard can't specify exactly what a moved from type must do because it doesn't know your codebase, but it does specify what the standard types do. E.g. a moved from unique_ptr is like it was set to nullptr, or a moved from vector is like it is empty. The absolute minimum is that it must be possible to destruct the object.
Any undefined behavior in the code isn't from the move, it's from treating the moved from object as if it still had its data without doing the proper bounds checks.
@@oracleoftroy You are right but it's not garanteed that the moved value stays in a valid/reusable state. For example, it's fine to just set a boolean to indicate to the destructor if a value is moved or not (and by fine, I just mean it works, not that we should do it !). So it is potential undefined behavior not by the compilers implementation but by how libraries work around this.
@@iamgly I do find that designing movable types tends to push for some sort of 'zero' state, even if normally I would prefer to discourage a 'default construct then assign' pattern. But I can't think of any sort of reasonable move constructor/assignment operator implementation that wouldn't allow at minimum reassignment after move. You need it for std::swap to work.
A bool flag can be fine if there isn't a more natural zero state, but you usually want move because you hold a pointer and are avoiding a deep copy, so usually there is something you could null instead. But move only types that aren't pointers can be great for some things (sockets, opengl wrappers), and not all of them have a natural default state value.
A swap based implementation can also be fine and avoids the extra bool member. As the moved from value is typically expiring soon anyway, there isn't a problem keeping the old values alive a bit longer and letting the destruction do the real cleanup. Such an implementation should obviously have no undefined behavior unless something else is seriously borked.
In my experience, it really isn't hard writing a move constructor/assignment operator that behaves properly without the gotchas you are imagining. It takes a bit of getting used to, but really isn't too different from any other resource management concern than normal copying and RAII deal with. Pro Rust arguments would be stronger if they showed more awareness and less fearmongering about the actual state of C++.
@@oracleoftroy Unfortunately, a Rustacean's arguments can never be stronger because if they actually knew C++ instead of spreading FUD about the language then they wouldn't bother using Rust as idiomatic C++ is equally as safe as Rust, faster to compile and execute, and allows for doing "unsafe" things without a stupid keyword.
@@anon_y_mousse I don't know if I would go quite that far. I think there is real value in the borrow checker. I'm not sure it is quite as high as Rust claims, but it isn't zero and I look forward to C++ borrowing the best parts of the borrow checker on future standards and implementations.
But that said, I rarely, though not never, have the sort of issues Rust seeks to protect against. The last time I ran into it, I was doing low level code for an emulator, the sort of code I think would need to be marked unsafe in Rust anyway. I was also writing the sort of code I knew wouldn't have lifetime issues, but I've tried to do in Rust and couldn't appease the borrow checker for the life of me. Often the examples of code Rust rejects but C++ accepts are the sort that would never pass review, but to Rust's credit, Rust finds it and C++ doesn't, and that has value.
I think for truly secure code, you still want static analyzers, sanitizers, fuzzers, and all the other sorts of tooling we have in C++ to help ensure correctness, even in Rust. A perfectly accurate borrow checker is probably a halting problem difficult undertaking, so Rust favors false positives over missing real issues. I'm not sure Rust's overly strict borrow checker is the right answer. Maybe a relaxed version that never has false positives but needs supplemental sanitizers, etc. to detect other issues would be better, especially since we probably still need them anyway.
When I was writing in Qt I noticed that they have an object.swap(other object) functions in their framework. Since then I always thought of move == swap and it kind-of behaves like this. I think maybe name swap would be better.
iirc, std::move takes the object you pass it and returns an r value allowing you to use move semantics on it. I don't think you're actually swapping anything.
Ok, wait wth. No. This is why people use move. So the destructor is NOT called and they can move the object. They DONT want to construct a new one. The destructor is called when the object falls out of scope. So, yes, it's called 4 times so there is this reference count in action (the mechanism of which I have no idea) but this seems like it's doing exactly what we want. Right?
I don't understand your reasoning. "moving" requires that there is more than 1 object in existence. It's *always* better to have fewer objects when you can. Fewer constructors, fewer destructors, less instruction cache pressure, less register allocation pressure... This is *always* a performance win.
The point of the video is how to get that performance win by avoiding both copies and moves and relying on copy and move elision, and directly constructing values in place.
For your own health, lets hope Bjarne is not seeing this. x)
Bjarne tends to agree with my Best Practices. He even regularly recommends my Commodore 64 talk to people. (I have people come up to me at conferences and tell me this.) I'm not worried about Bjarne seeing this episode.
The paranoia of this man to sacrifice code complexity and readability to avoid calling delete on null pointers...
This paints a terrible example of writing code in general. There is nothing wrong with std::move.
And what if I want to get multiple types of data and do some operations with them before pushing them in a container? Is it really better to make a lambda for every data type?
You appear to have missed the point of this episode - if you structure your code in such a way that you don't need or want std::move, then you will get more efficient, composable, and cleaner code in general.
Can anyone tell me why he uses puts instead of cout
Fast compile times, smaller binaries, does exactly what I want, and makes for more readable assembly output.
th-cam.com/video/VZTVmKOXLVU/w-d-xo.html
Thank you very much@@cppweekly
I use std::cout in Compiler Explorer and just out of curiosity wondering why you don't use std::cout instead of std::puts.
More assembly code for similar result
you should use std::format\std::print(std::println) instead (or fmt library if you don't have c++23 support yet). Iostreams have a hidden cost of synchronizing with the c stdio library; plus library has better formatting syntax and is type safe
@@Raspredval1337 I'm not worried about runtime cost in this case - I'm just trying to generate cleaner assembly that's easier to read. `puts` is effectively "free" when it comes to the assembly output.
I'd really love to have you in for some training, but apparently it is "too expensive"... 😢
Alright, call me a d-bag, because I am but here is my clapback: Does anyone hire you (OR ANYONE) for on-site training? I've worked at AMD and a company of 5 people and never had on-site training except for a specific product we bought (Jenkins). Employers want to test senior applicants on this sort of material in the interview and MAYBE have them teach the junior and mid devs. Or just have them pick it up through code comments and code reviews, not through specifically scheduling training between the two.
After that, I didn't fully follow the video. Your example, just before the lambda was making 4 instances of the object, explicitly. Then, you made the lambda and had the array members filled out with that. As you know, this is quite different from the code just before that. I see you're sort of showing that you can initialize in place and add to the array all at once and you did explicitly state how the compiler is taking care of single moves (without using implicit moves or anything since yours are explicit) but Im otherwise not connecting these behaviors before and after the refactor. They're very different to me.
Ok after rubber ducking it a bit, writing the above statement, I think I'm following what you're exemplifying better but still am lacking to come to the same conclusion you are.
Either way, I appreciate the video. I understand your frustration a bit since your goal is to get hired for on site training but I feel the responses you got that brought you to make this video could be easily foreseen.
Edit: Ok after I walked away and thought a moment, I realized (afaik) people think that the std::move somehow totally would save a copy/construction operation and you were showing it does not. I guess the reason I didn't get this is because I am aware that the code would behave just as you showed it would.
Yes, many companies hire me and many other trainers for on-site training. This is actually a very normal thing. I suggest you look around for different companies that care about investing into the education of their developers.
You need to chill on the hiring calls, it's a bit desperate sounding IMO honestly sorry. Interesting thought though that I hadn't considered, using RVO to avoid any copies or moves entirely.
Waiting for your Rust course!
Shill moar!
If C++ didn't allow the use of moved values this wouldn't be a question. But misleading, as the example is just for a "factory" pattern, there are plenty of examples where you should move.
Bit of a perennial this one. But still always a crowd pleaser.
I will not contract you for my company! I m no dummy.
Jetbrains stuff is awful, no matter how much money they throw into marketing.
Compared to what? Serious question.
- Visual Studio is probably still the most functional C++ IDE, IMHO, but it's still not fully cross-platform, and much of my work is done on Linux. However, JetBrains has its ReSharper products that enhance it even further.
- Eclipse is still pretty decent for Java, but not great at anything else, and its design is heavily weighed down by legacy.
- I can't think of any IDE that even approaches PyCharm's support for Python.
- VSCode is a good programmer-oriented editor, but requires a lot of configuration as a DIY IDE, and I already have Vim/NeoVim as an option. However, VSCode's Vim/NeoVim plugins bring all that power to VSCode, too, which is nice.
JetBrains products are very competitive with all of these IDE options, and it has a very good modern foundation in its IDEA platform that no other platform is competing with. VS Code is the closest anyone has come, but it lacks the "batteries included" aspect of JetBrains' language/task-specific IDEs -- but it's free, which is a fair tradeoff.
CLion is a godsend.
@@_supervolcano sent by a Jewish-like god as a punishment for working with projects that are not even that large.
Nope, you're wrong. Go back to your VS Code.
@@adamkonrad, I prefer Visual Studio Proper and Qt Creator, but VS Code also works extremely well, for an Electron app it's amazing. Jetbrains IDEs, on the other hand, can load even a 32-core CPU to 80% with bullshit background tasks that run for minutes.
Interesting... do you guys think Jason is ready to hate C++ and love Rust? 🙂
No, not at all
Rust has its own problems, and tbh the borrow checker doesn't really offer anything to an expert likie Jason. Still I wish c++ had destructive move.
@@TsvetanDimitrov1976 "has its problems"... lol. The arrogance...
You're not smart enough to handle a million states in your head. Pull your head out of your butt and smell the reality. Many people who are masters in C and C++ have been doing memory bugs, including whoever you consider your mentor. Guaranteed. When you say the borrow checker offers "nothing" in a video that shows how C++ move is broken... well, you have a few more lessons to learn in being humble, Mr. Expert.
And btw, I did C++ for 15 years. But I'm not arrogant enough to think that my brain is better than compilers to track tons of states.
@@TsvetanDimitrov1976aren't destructive move less optimized than simple ownership transfer? since you have to flag in some way that the object is now in "destroyed state"
At this point he might just be ready to hate people who leave Rust comments on C++ videos every week like I am. I do like Rust though.
Oh man. I like Jason's talks on CppCon. So was eager to check this channel out. Not quite what I was expecting. I have seen the gist of this topic explained in at least a dozen stackoverflow threads. So why the constant bombardments of on-site training? It's such a clear topic to explain through code example.
because my man needs to eat