Тёмный

Back to Basics: RAII and the Rule of Zero - Arthur O'Dwyer - CppCon 2019 

CppCon
Подписаться 153 тыс.
Просмотров 79 тыс.
50% 1

Опубликовано:

 

3 окт 2024

Поделиться:

Ссылка:

Скачать:

Готовим ссылку...

Добавить в:

Мой плейлист
Посмотреть позже
Комментарии : 53   
@paxdei1988
@paxdei1988 Год назад
Finally, a CppCon talk I can actually follow and understand!
@jonathanlecavelier3591
@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
@yash1152
@yash1152 3 месяца назад
thanks for the review. it gives me enough faith to [try] watching this complete
@MagnificentImbecil
@MagnificentImbecil 2 года назад
I have been able to learn much from the presentation. Thank you for the work done for all of us !!
@CppCon
@CppCon 2 года назад
Great to hear!
@phonlolol5153
@phonlolol5153 4 года назад
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.
@Solarbonite
@Solarbonite 2 года назад
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
@jiaweihe1244 Год назад
if you just copy into the orignal storage, in example #3 of the talk, you will overwrite the value you are copied from
@babitagurung6724
@babitagurung6724 Год назад
I am taking Advance C++ this semester. This really help me understand more about RAII. Thank you.
@BGFutureBG
@BGFutureBG 4 года назад
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.
@20thCB
@20thCB 3 года назад
@12:05 even pros forget the [] after delete, what hope have we mere mortals?!
@frydac
@frydac 3 года назад
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.
@nhanNguyen-wo8fy
@nhanNguyen-wo8fy 11 месяцев назад
11:00 rule of three 18:50 similiar to lock 31:42 rule of 5 43:16 friend function
@vikaskumarsharma2322
@vikaskumarsharma2322 3 года назад
Great Talk! very well presented. Thank you for the presentation.
@CppCon
@CppCon 3 года назад
Glad you enjoyed it!
@佐邱鸣
@佐邱鸣 4 года назад
Loud and clear!
@marcospatton
@marcospatton 2 года назад
great talk !!! Clearned up for me, thanks
@ethiogofund7520
@ethiogofund7520 10 месяцев назад
very basic, yet important, core and backbone concept of C++
@MagnificentImbecil
@MagnificentImbecil 2 года назад
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.
@FalcoGer
@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
@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.
@佐邱鸣
@佐邱鸣 4 года назад
I like this guy!
@anatheistsopinion9974
@anatheistsopinion9974 4 года назад
Me too :)
@TheOnlyAndreySotnikov
@TheOnlyAndreySotnikov 2 года назад
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.
@Quuxplusone
@Quuxplusone 2 года назад
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
@TheOnlyAndreySotnikov
@TheOnlyAndreySotnikov 2 года назад
@@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.
@MyYuppe
@MyYuppe 2 года назад
Great talk!!!
@deanroddey2881
@deanroddey2881 3 года назад
I've always called them 'janitorial objects', since they clean things up. It's an obvious term.
@MagnificentImbecil
@MagnificentImbecil 2 года назад
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
@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
@MrSparc
@MrSparc 4 года назад
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.
@beefbox
@beefbox 4 года назад
Probably not enough space on the slide
@FerhatErata
@FerhatErata 4 года назад
Great Presentation!
@Quancept
@Quancept 11 месяцев назад
fantastic!
@movax20h
@movax20h 4 года назад
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.
@Solarbonite
@Solarbonite 2 года назад
What about making them move-only or emplace-only types? Is there still a danger? Asking for a friend. 😁
@pleaseexplain4396
@pleaseexplain4396 Год назад
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
@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 ).
@ldmnyblzs
@ldmnyblzs 5 лет назад
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.
@OMGclueless
@OMGclueless 4 года назад
@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.
@antonfernando8409
@antonfernando8409 2 года назад
awesome.
@Cromius771
@Cromius771 4 года назад
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.
@The2bdkid
@The2bdkid 4 года назад
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.
@Solarbonite
@Solarbonite 2 года назад
In fairness it's basically an unneeded if block. 9999/10000 times you'll be copying a different object instead of itself.
@rafalmichalski4893
@rafalmichalski4893 4 года назад
Shouldn't be (slide 58) ?: std::copy(&rhs.uptr_[0], &rhs.uptr_[0] + size_, &uptr_[0]);
@OndrejPopp
@OndrejPopp 3 года назад
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.ramirez4305
@josee.ramirez4305 3 года назад
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
@Swedishnbkongu
@Swedishnbkongu 3 года назад
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
@zofe
@zofe 3 года назад
Explicitly - rather NOT directly, because all handling of a resource by a class owning to it is done directly.
@ElPikacupacabra
@ElPikacupacabra 2 года назад
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.
@Solarbonite
@Solarbonite 2 года назад
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.
@masheroz
@masheroz 2 года назад
Ever forget to close a file?
Далее
Лиса🦊 УЖЕ НА ВСЕХ ПЛОЩАДКАХ!
00:24
#kikakim
00:10
Просмотров 14 млн
Back to Basics: Concurrency - Arthur O'Dwyer - CppCon 2020
1:04:28
CppCon 2019: Jason Turner “The Best Parts of C++"
58:36
The Value of Source Code
17:46
Просмотров 50 тыс.
Branchless Programming in C++ - Fedor Pikus - CppCon 2021
1:03:57