Finally, a CppCon talk I can actually follow and understand!
@jonathanlecavelier3591 Жыл бұрын
Too bad the talk is from a registered sex offender who drugged and raped his victim before getting caught by the popo with CP on his computer
@yash11525 ай бұрын
thanks for the review. it gives me enough faith to [try] watching this complete
@phonlolol51535 жыл бұрын
Great talk. Step by step introduction. But i feel one thing is missing, and that is the big disadvantage of the copy and swap idiom: Copy-and-swap will not make use of already aquired resources. For example alocated storage. So in the naive vector implementation, in the copy assignment operator you could check if the target instance already owns enough storage for holding all elements and just copy all elements. Copy-and-swap will always discard all already allocated storage and make a new one! Doing so will only give you the basic exception safety (instead of strong), but thats a tradeof for performance.
@Solarbonite2 жыл бұрын
For tree types couldn't that still lead to the same bug? Like v = v[0]? As you're copying over the object it'll nuke itself after the first element is copied
@jiaweihe1244 Жыл бұрын
if you just copy into the orignal storage, in example #3 of the talk, you will overwrite the value you are copied from
@MagnificentImbecil2 жыл бұрын
I have been able to learn much from the presentation. Thank you for the work done for all of us !!
@CppCon2 жыл бұрын
Great to hear!
@babitagurung6724 Жыл бұрын
I am taking Advance C++ this semester. This really help me understand more about RAII. Thank you.
@BGFutureBG4 жыл бұрын
The initialization vs assignment thing had me baffled for years. I knew about like everything discussed here BUT that. Goes to show some revisions of things you think you know are definitely worth it.
@nhanNguyen-wo8fy Жыл бұрын
11:00 rule of three 18:50 similiar to lock 31:42 rule of 5 43:16 friend function
@20thCB3 жыл бұрын
@12:05 even pros forget the [] after delete, what hope have we mere mortals?!
@frydac3 жыл бұрын
You don't need new and delete, just use std::unique_ptr with std::make_unique, and std::vector in 99% of cases where you need heap allocated memory.
@ethiogofund7520 Жыл бұрын
very basic, yet important, core and backbone concept of C++
@FalcoGer Жыл бұрын
37:10 i think the correct thing to do is template that and use std::forward. When an r value is passed then this doesn't decay, if an l value is passed, then this decays into a reference. forward then calls the correct constructor. template requires (std::is_convertible_v) NaiveVector& NaiveVector::operator=(T&& rhs) { T temporary{std::forward(rhs)}; temporary.swap(*this); return *this; } 1:00:10 I believe the STL doesn't use the by value assignment operator because it's less performant. There is a talk on perfect forwarding that counts the amount of allocations, copies and moves and explores the different gotchas with each approach to implement this and the drawbacks like code duplication and the like. v=PNRju6_yn3o
@atib1980 Жыл бұрын
Hi @FalcoGer. What you wrote is basically a member function template, a universal assigment operator that accepts any type by a forwarding reference (a type T which can be implicity converted to NaiveVector. This means that if you for example pass an l-value int or an r-value (temporary or std::move()-ed object) and your NaiveVector class has a non-explicit constructor that takes an int then you could in theory write NaiveVector nv(10); nv = 5; and the generated function might look like the following: NaiveVector& NaiveVector::operator(int rhs or int& rhs) { int or int& temporary{std::forward(rhs)}; temporary.swap(*this); return *this; } So if you pass a temporary int or std::move-ed int variable or an l-value reference (int& variable) to template NaiveVector& NaiveVector::operator(T&& rhs) then the compiler won't be able to generate a valid member function without compiler errors because there is no member function named swap defined on int and also semantically speaking swapping int with a NaiveVector doesn't make any sense. But if you pass an l-value NaiveVector object or a temporary or std::move()-ed NaiveVector object to it then the generated code does the right thing. It initialized NaiveVector& temporary with a NaiveVector& rhs l-value reference or it calls the move ctor of NaiveVector in order to create temporary (a NaiveVector object created by stealing the contents of rhs) NaiveVector temporary(NaiveVector&& rhs) and then it does the correct swapping and returns the swapped NaiveVector object (*this) by l-value reference. However Arthur's NaiveVector& operator=(NaiveVector rhs) "noexcept" copy assignment operator which is implemented based on the CAS idiom would work just fine as well because rhs is either contructed using NaiveVector's copy ctor or NaiveVector's move ctor and the user of NaiveVector's copy assignment operator decides how they wish to call NaiveVector's assigment operator. (NaiveVector a(10); NaiveVector b(10); ... a = b; // rhs is a copy of b created using NaiveVector's copy ctor or a = std::move(b); a = NaiveVector(10); or a = 10; // rhs is created using NaiveVector's move ctor.
@TheOnlyAndreySotnikov2 жыл бұрын
36:52 The problem with the by-value assignment operator is that the compiler provides a default move assignment operator only if all members have a move assignment operator. Not "move assignable", but "have a move assignment operator". So if your class Foo has a by-value assignment operator, and you write "struct Boo { Foo foo; };", Boo will NOT have a default move assignment operator. If Foo on the other hand have a move assignment operator instead of a by-value assignment operator, the compiler will provide a default move assignment operator for Boo as well.
@Quuxplusone2 жыл бұрын
I might be misunderstanding what you wrote; but if I understood correctly, what you said is wrong. There are two independent decisions the compiler makes here. First, the decision _whether_ to implicitly default the move-assignment operator for `Boo` is completely unaffected by the properties of `Boo`'s data members; it depends only on what user-provided special members `Boo` has declared. Second, the decision of how `Boo`'s defaulted operator _behaves_: It will always move-assign each member individually, doing overload resolution to select the most appropriate assignment operator as usual. If one member is C++98 Rule-of-Three "copy-only" and another is C++11 move-enabled, the "copy-only" member will be copied and the move-enabled member will be moved. If one member has a by-value assignment operator, that member's by-value assignment operator will be called. If overload resolution on operator= fails for any member, or the best match is inaccessible or deleted, then `Boo`'s whole defaulted operator= will be defined as deleted. Here's an example: godbolt.org/z/jfoh18cYW
@TheOnlyAndreySotnikov2 жыл бұрын
@@Quuxplusone Watch Sean Parent's "Better Code: Runtime Polymorphism" presentation. He demonstrates the problem with the by-value assignment operator at 29:10. You also can read about the underlying problem in C++ core issue 1402.
@MagnificentImbecil2 жыл бұрын
At timestamp 15:30: "As soon as I access `rhs`, [bad things happen]`, ...": The `delete [] ptr;` instruction has not affected `rhs`, it has only affected `*this`. The problem is different: when the element type `T` is a user-supplied type that we, the implementers of a "generic container of `T` elements" do not exactly know (because we wish to be generic), its operations might (construction, assignment) might fail: - creating an array of default-initialized `T`'s might fail, but at that point we cannot go back to `*this`'s old contents (before propagating the exception) because we have already `delete [] this->ptr` in our first line (without even zero-ing out `this->ptr` and `this->size` -- not that I recommend the practice of zero-ing out things); - additionally, assigning the 4th `T` in the `copy` call might fail (after 3 successful assignments and before the remaining 6, which are then never attempted); not only have we already let go of the old `T`'s managed by `*this`, but now we manage a new array of `T`'s, some of them copied from `rhs`, the rest of them default-constructed, and our `size` is wrong.
@MagnificentImbecil2 жыл бұрын
At timestamp 32:15: Move operations should be provided not just as optimizations, but actually for correctness. Move operations are not just faster than copy operations. Because move operations only deal with variables of fundamental types (e.g. a pointer to an array of `T`'s, a "size" integral and a "capacity" integral) or strongly-error-safe user-defined types (e.g. `unique_ptr 's) in the kernels of `*this` and `rhs`, they are also safer -- they provide the strongest error-safety guarantee -- they are never going to fail (and should be annotated with `noexcept`). Lower-level code's offering of stronger guarantees allows higher-level code to offer stronger guarantees, e.g. change the higher-level guarantee from "basic guarantee" (on error, resources are not leaked and low-level invariants still hold, but the higher-level state might have been modified and generally be unpredictable => generally not usable for high-level application correctness) to "strong guarantee" (on error, the lowlevel state and the high-level state are not modified). E g.: With no-fail move operations defined, `std::swap` itself improves not just its speed, but also its guarantee of behaviour in presence of exceptions/errors (from "basic guarantee" to not just the "strong guarantee" but, incidentally, actually to the even stronger "no-fail" guarantee).
@tk36_real Жыл бұрын
if `swap` is `noexcept` you can mark `T & operator=(T)` with `noexcept` too, because that doesn't effect how the arguments are created
@vikaskumarsharma23223 жыл бұрын
Great Talk! very well presented. Thank you for the presentation.
@CppCon3 жыл бұрын
Glad you enjoyed it!
@MrSparc4 жыл бұрын
In the slide 13 displayed at 6:21 the method push_back() has an error: the 'newvalue' argument is never added to the allocated vector, resulting in an undefined value at this vector position.
@beefbox4 жыл бұрын
Probably not enough space on the slide
@pleaseexplain43962 жыл бұрын
Can someone explain how implementing a friend swap will encourage the compiler to invoke that swap that Arthur tries to explain at 24:50? I wasn't really able to wrap my head around it!
@attilatoth1396 Жыл бұрын
My guess is, to make the user able to use std::swap(v1, v2) for instance, without having to call swap on a class instance (e.g. v1.swap(v2) which can be used inside the Vec class because it had a member function swap as well ).
@deanroddey28813 жыл бұрын
I've always called them 'janitorial objects', since they clean things up. It's an obvious term.
@marcospatton2 жыл бұрын
great talk !!! Clearned up for me, thanks
@ldmnyblzs5 жыл бұрын
I guess the STL doesn't use by-value assignment because it would require the implementation to use copy-and-swap which would be an unnecessary restriction in the standard.
@OMGclueless4 жыл бұрын
@Steve Thibault This isn't it, I don't think. If you store a polymorphic base class T in a std::vector you already get slicing, regardless of whether the assignment operator is by-value or not. The reason they don't do it is just for efficiency.
@佐邱鸣4 жыл бұрын
Loud and clear!
@佐邱鸣4 жыл бұрын
I like this guy!
@anatheistsopinion99744 жыл бұрын
Me too :)
@movax20h4 жыл бұрын
11:20 About RAII and puting locks as members of classes/structs. Don't do it. Never put locks in the classes as members. Never make such objects copyable. Double locking or recursive locking in general is a bad idea. Mutexes that implement recursive locking are inherently less performance, and trying to use them is just a sign of bad desing in the first place. I did wrote a lot of multithreaded code and there is never a need for using recursive locking. If you need one, that means your design of locking and APIs are bad.
@Solarbonite2 жыл бұрын
What about making them move-only or emplace-only types? Is there still a danger? Asking for a friend. 😁
@MyYuppe2 жыл бұрын
Great talk!!!
@FerhatErata5 жыл бұрын
Great Presentation!
@Quancept Жыл бұрын
fantastic!
@Cromius7714 жыл бұрын
Why doesn't his assignment operator check if its the same object and return? STL vector does this. His answer about nested references made no sense to me.
@The2bdkid4 жыл бұрын
I think his point was that a self-assignment check doesn't necessarily guarantee nothing bad will happen. His recursive example shows how that can happen. Therefore, since a self-assignment check can't guarantee nothing bad will happen, it's not required. That's of course in the general case. If it makes sense for something special to happen during self assignment in your class, then implement it as necessary. Eg vector to vector assignment shouldn't destroy the owned data.
@Solarbonite2 жыл бұрын
In fairness it's basically an unneeded if block. 9999/10000 times you'll be copying a different object instead of itself.
@antonfernando84093 жыл бұрын
awesome.
@rafalmichalski48935 жыл бұрын
Shouldn't be (slide 58) ?: std::copy(&rhs.uptr_[0], &rhs.uptr_[0] + size_, &uptr_[0]);
@OndrejPopp3 жыл бұрын
I was thinking exactly the same thing, had to read through all the comments to see if someone already mentioned this. And here it is, the last comment 😃
@josee.ramirez43053 жыл бұрын
This seems like a lot of friction just to code, I think I prefer C-style coding, I mean automatic construction destruction is cool because it occurs automatically, the problem is that a lot of bugs are going to be "invisible" at plain sight just like he shows at the beginning. I rather being explicit
@Swedishnbkongu3 жыл бұрын
It's not invisible to a C++ programmer because we see exactly how it should work. As he mentioned it also covers more cases with more safety than C-style, like exceptions. It also means you write the pattern once in the class, and the user can't mess it up. Most C++ programs barely if at all need to actually think about allocation and freeing because of how good RAII works
@zofe3 жыл бұрын
Explicitly - rather NOT directly, because all handling of a resource by a class owning to it is done directly.
@ElPikacupacabra2 жыл бұрын
RAII is a great idea, but it only underscores for me how much I hate OOP. Lazy software design that is paid for in a different place, and with a different type of effort.
@Solarbonite2 жыл бұрын
Well it can certainly mask performance issues, but things like locking is much easier with RAII. Locks are annoying, especially if you want to use exceptions.