What do you guys think? How often do you see weak pointers? 👇 Also don’t forget you can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
I tend to have a preference for encapsulation, so that I know exactly what's going on in memory both in terms of code size and memory allocation. Sometimes the documentation, even for standard libraries, let's admit it, can be a bit obtuse and I often run into the problem where I had to do things the hard way anyway just to understand what the standard is doing, at all. I'm the kinda guy that would be very fastidious about what goes on in the stack, even though I run a system that has an obscene amount of memory for the application set that I run, because it affords you some additional freedom in terms of what kind of optimizations you can implement as far as data access within code that "technically" works just fine, but might have an issue with readability.
I just had a case at work one week ago where changing some shared_ptr to weak_ptr would have solved the immediate problem. Unfortunately, the whole design was flawed and the real solution was to get rid of shared_ptr completely. In my opinion, real shared ownership is quite rare and most of the time shared_ptr usage is hinting at a design flaw.
I think with Data Oriented Design, you don't need pointers. use ECS. unique indexes. don't use objects. manage the lifetime of your entities by moving their traits into or out of various arrays of that trait. Relational databases don't need pointers. neither do any data structure, it can always be done with integer indexes replacing pointers.
15:25 removing the observer from the vector while iterating invalidates the current iterator and would not work. You'd need to use the iterator returned from erase() instead of the range-for-loop here.
It really does seem like there should be at least a clang-tidy check for stuff like this, but I guess it can be hard to detect when it's several functions deep and across TU borders.
Yep, and this is true for all programming languages that support list/vector iteration (I think). Need to pay attention to element removal during iteration. Replacing iterator with the return value of erase() is the way to go.
A good case for weak pointer is back referencing. If a class owns another but you want to able to go from the child to the parent, then you can use weak pointers. Something like child1->parent->func(). You know your parent is alive and this way you avoid cyclic dependecies.
Writing your own smart pointers is probably one of the best learning exercises for C++ in my opinion. Obviously not everyone is going to have a use case for a custom smart pointer implementation, but it teaches you a lot about templating, object lifetime and operator overloading, just to scratch the surface.
This is a excellent observation. I have done this more than once. The ability to write your own smart pointer should be a fundamental skill that one learns when learning C++. Knowing how to do this teaches you RAII, which is useful for many things besides memory management. It also forces you the better understand the overhead of managing memory. If you can write a smart pointer for memory, you can do the same for managing any other resource as well.
Agreed. Writing any sort of RAII class, like a custom data structure, is also a good way to learn about data ownership and lifetimes. Some of my most instructive moments in C++ were when I realized that a strange bug was only present because I hadn't followed the rule of three (or five). It also helps to learn rust, which forces you to think about data ownership
15:14 Removing an element from a vector while you're currently iterating over said vector will invalidate the reference to the current element in the for loop. In this case, you're holding the current element by value, so it's not a problem. But if you're holding it as a reference, weird juju happens.
I was wondering about that. I know C# throws an exception if you try to do this, so I was wondering if C++ had any similar rules/protections for mutating collections with an active iterator. Unsurprisingly, it seems like "shooting yourself in the foot" is allowed.
Just a note. shared_ptr is not guaranteed to be implemented with reference counting. Therefore use_count might be a linear operation, while expired is always constant. 13:45 I don't like this example. Smart pointers express ownership. In this case, if you can just switch to a weak pointer, then there was no ownership in the first place, and using a shared was an error. That's a case for raw pointers or references. The number places where you need a shared_ptr is massively smaller than the places where people use them.
Good evening Mr. Yan , I want to thank you for your outstanding work on the game engine series and on your content on you UA-cam, it was really helpful to me. One of the series that i really liked was the "Ray Tracing" series and I know that you have said that it's better not to be a series on UA-cam and you didn't want to continue it there but i am asking you to consider continuing it because it's one of the greatest series on your channel ( it talks about graphics more than any other series- game engine series is focused on game engines and c++ series is on c++ , even opengl series we didn't have that much of episodes ). Thank you so much ❤❤ Have a nice day 🌹
8:20, nitpick :) This is not quite true that we can't know this. We can provide custom deleter for the shared pointer to Object that would store ref to manager and set manager.obj to nullptr shortly before Object is destroyed. The real reason is that this is still not thread safe. The object might be destroyed from a different thread after the if statement returned true. It would need to be synchronised, but we do not reinvent the wheel, cause we already have STL solution, which is std::weak_ptr
I suppose, but a custom deleter means now the Object would have a dependency on the 'manager' class. Object shared pointer has to have 'manager dependent' information (that it has to update its ref and set manager.obj to nullptr). I'd vote against this and stay with std::weak_ptr.
I'd definitely switch to the safer code in event driven situations like these input observers since they aren't likely to get called thousands of time a second. For anything else, I'd measure and then make that decision.
Nice neat example ! Great description and explanations. Be careful though when you call RemoveObserver(observer) inside the for loop over the observers. That may lead to undefined behavior, right ?
Depends on the container used to store observers. For instance, list does not Invalide iterators on remove, so you should be fine. Vector, on the other hand, does invalidate iterators on remove, so this may be UB.
Weird reactions to making the code safer... Personally recently went through a fundamentals course and considering all the overhead that you would normally need to manage (not just knowing lifetime but also overloading stuff) I don't understand why people can't see that this is a good starting point to ensure your code scales safely. Then if there are performance issues (somehow) work on your own better version. Remember, Java was created to prevent some most of the memory issues that existed before smart pointers were introduced. And a lot of companies moved to Java because of it.
My ex excompany did use them always. But somehow say ended up using ton of threadlocks and workarounds instead of doing stuff like obj.lock or weakptrs. Guess it has todo with knowledge and design. In the end it was just threadlocks and ptrs that doesn’t got deleted🎉
I'm little bit new to the c++, but I have question, does auto obj = obj.lock() increment the reference count of the original obj? becuse obj.lock() method returns shared_ptr
Firstly, auto obj = obj.lock() is invalid C++ because you cannot have two objects with the same name (obj). I'm sure that's just a typo, but it's one of the reasons we avoid using variable names that only differ by case (Obj and obj). The Cherno really know better! Secondly, the return value of weak_ptr::lock() depends on whether weak_ptr::expired() evaluates true or false. If true, the return value is an empty shared_ptr which obviously does not increment any reference counts. If false, the original object is still alive, thus you get a new shared_ptr constructed from the weak_ptr, which naturally increments the reference count. Note that if you attempt to explicitly construct a shared_ptr from a weak_ptr, an exception is thrown if the weak_ptr has expired. This doesn't happen with lock(), which is an atomic operation.
The only question I would ask is, do you think there's something wrong with the standard pointer types that you would choose not to just use them and instead wrote your own?
Hey @The Cherno , what tool do you use to draw stuff on the screen while teaching, I mean the red markets highlight stuff. Does anyone knows this? kindly drop a comment
I like it a lot your explanation. I have a super convenient use case for it. I am making a game where I can shoot among others homing missiles. Those missles hold a weak_ptr to the object they are aiming to. So I dont need to handle the situation when the object gets destroyed (I can more or less just delete it) and the missles can handle the situation then
You can use some sort of entity system, that maps IDs to entities. This way, you would not have to use weak_ptr and instead just hold onto an ID of the target
I never really understood weak ptr, till now heh. Might of been better than using shared pointer in some places. Where I couldn't figure out why I had memory leaks and such. I was probably creating things that would never get deleted.
I love it when i compare this to Swift's Automatic Reference Counter. That one does this by default to all memory, and it is pretty slick. Nice to know C++ has it too.
Is a weak pointer more efficient than doing something like if(pointer), or is it just more safe? I don't really get why people seem to be so against raw pointers, I mean, if something is going to go wrong, make it as loud as possible for easy debugging, lol.
You're actually arguing *for* weak pointers. They're slower, but safer. If you're just holding onto a raw pointer then it is possible that the object was deleted elsewhere. Nothing tells you this. It's also (quite) possible that after the object was deleted it was reallocated as a different type (or even the same type) and that can cause a very difficult to diagnose bug in your code. Using a weak_ptr ensures that your "if(pointer)" (technically the .lock() conversion to a shared_ptr) check always returns null if the object has been deleted. It also ensures that the object remains valid while using it unlike a raw pointer. So if you're not 100% sure of the lifetime and ownership of an object, it's worth looking into using weak_ptrs. If you are, then unique_ptr is likely a better choice than a raw pointer as it makes ownership explicit. Even when using these types, you're still left deciding which type to use for function parameters (i.e. do you use a raw pointer, a reference, or a const unique_ptr& for a function parameter that use an object stored in a unique_ptr).
@@Ziflinz I do understand what you mean, especially when you're dealing with large code bases, but when you say "nothing tells you if a pointer was deleted elsewhere", that is kind of why I prefer the raw pointer. Because if you get a big juicy error accessing an invalid pointer that hasn't been properly handled, you can then go and setup break points to find the problem. With something setup in a "safe" way, it is not always immediately apparent what the problem is when literally nothing happens, because the pointer is gone and the weak_ptr handles the falloff for you. In my opinion, if a pointer is being deleted when it's not supposed to be, the structure of the code is not ideal, and the sooner you know, the sooner you can restructure.
Honestly 99% of the time you probably don't even need shared pointers. Organize your code to avoid returning structures which contain arbitrary references. For most tasks, the lifetimes of your data should be pretty obvious.
@@-rya1146 Majority of the time it is, there's very few cases where it won't be, mostly in multi-threading or maybe if your consuming someone else's work. (and even then good documentation handles 90% of the latter cases)
This. When I see a shared pointer in your single-threaded code, then I must assume you have no idea of your data flow. Red flag. Multi-threaded they can come in handy, but if there's a way to structure your flow to not need them, then really just don't use them.
@@Dienes why wouldn't you always design your code to be thread safe? if it's public, assume someone will use it with data that eventually scales to a million iterations, and assume it will be used asynchronously - because invariably someone will assume your api scales infinitely and is thread safe.
This mentality is exactly why most programs has leaked memory problems, and why people are opting out of Cpp and going for the garbage ass programming language that is rust.
I'll usually prefer unique_ptr and raw pointers for that project, a ref counted pointer is kinda pointless and wasteful unless you expect your objects to constantly go out of scope without moving their pointer object around or if you're doing multi-threaded work, something which did not happen in that project. I've found that in most single threaded programs its uncommon for me not to know when the object will go out of scope unless I build my project with exceptions.
Hello The Cherno. I submitted an inquiry email a week ago to know more about the engine before becoming a Patreon, and potentially a volunteer. I still didn't get any answers for it.
"cyclical shared pointers means the objects can never die" We're *this* close to hearing you talk about why garbage collection is a good idea, actually!
It is have been a year since I found your Chanel and every of your videos is perfect, thank you cherno, can I ask you something Why your name is Slavic, just want to know.
I think the important thing to remember is smart pointers is for ownership. When your code needs to own some other object on the heap, use smart pointers. If your function someplace just needs to work on a pointed to object (and not save it for later)? Just use raw pointers, call the function with the raw pointer from your smart pointer.
If you need to work without saving it yourself you should be taking a reference to an already existing shared/unique ptr for the duration of your call not taking raw pointers, you're borrowing the ownership of your caller and guarenteeing that you wont accidentally do something stupid with a raw pointer
@@TurtleKwitty The idea is fine, but it does not work fully. You make a function that does something with some type. you don't want a copy so you take it by pointer. Ahh but you heard raw pointers are evil, so you use a smart pointer and take it by ref because you don't actually want the ownership. But there are two smart pointer so now you need to make two overloads, and it only works for object contained in a smart pointer. We might still need to be able to work with stack allocated objects I think the reference is a good idea, but then do it on the contained type. Or make a non_owning pointer wrapper to be explicit about it being non owning if you don't like raw pointers. Like non_owning or something.
@@TheTykbry just take the red to the stack what the fuck xD not using raw pointers doesn't mean you randomly throw things into shared ptr for no reason XD
@@TurtleKwitty yes you don't. Exactly. But you suggested taking the smart pointer by ref, now forcing the user to do exactly that. So you should just take the parameter of what the smart pointer points to, lets say a struct S. So your function could just look like (foo(S& s) or foo(S* s), and then you would call it with a smart pointer: foo(*s_ptr.get()) for calling the ref version or foo(s_ptr.get()) for the pointer version. And now foo even works for non smart pointer types. Compared to what you suggested originally by taking the smart pointer by ref, somehting like this: void foo(std::shared_ptr& s), forcing every user to use a smart pointer. Got a local S or some other kind of stack bases S? Or maybe unique_ptr? Too bad, put that in a shard_ptr!
@@TheTykbry Ref yes, pointer no. The entire point is to eliminate possible foot guns, which converting back to a pointer would not do. So yes if your structure isn't always on the heap ref to it directly, if you always have a pointer ref to it directly. Your original post was about handling cases where functions take things by raw pointers to nit have the ownership overhead so the answer to the specific thing you brought up is red to the smart pointer. But yes as a general rule if you're doing things from scratch use refs everywhere possible
Unique pointer has the sole ownership of an object. When the object goes out of scope, the unique pointer cleans it up. This means you can't copy a unique pointer, thus making it not-unique. A shared pointer has a shared ownership of an object. Every time the shared pointer is copied, the objects reference count increases, and every time a copy goes out of scope, the reference count of the object decreases. Only when the objects ref count hits zero is the object cleaned up. This prevents null pointer undefined behavior.
I think in all my C++ code I've ever written there was only a single case where shared and weap ptr would have been useful, but I used a raw ptr instead, because the cyclic reference was managed by the 2 cyclic classes being instantiated only once as RAII objects not as heap allocated memory. I will keep them in mind though.
Personally, I am a fan of raw pointers. Probably because when I started to work at my current company, I inherited a project to maintain that was full of std::shared_ptr. Most of them were unnecessary or incorrectly used. There was a case of a singleton that kept on dying because implementation was depended on a std::shared_ptr living throughout the program's live span. If it died, the next call to instance would create a new one and you ended up with total weird behavior that was very very very hard to track down. It was very satisfying when I did though.
For me it sounds like that you MUST use smart pointers ALWAYS until you know that it's usage in exact place cause NOTICEABLE overhead. First make it safe. Optimize later.
Yep, that's a typical case of premature optimization. It's compounded by the fact that C++ and Rust make the simple, safe case far more awkward to write.
I feel like you’ve solved a problem that wasn’t a problem. Is it all just to save a 4 bits integer value on stack? In the example, the pointed object was deleted, but there was still an integer storing the adress. Why bother using weak pointer instead of raw pointers? I’ll watch it again.
The problem is that if the original pointer gets deleted, the memory it points to becomes invalid. So if you try to use your pointer to call a method or whatever, the application is going to crash because the address that your pointer is pointing to no longer contains the object.
the observer doesn't know (and shouldn't know) anything about the lifetime of the pointers it's holding onto. There was no other safety from invalid pointers during iteration so either they never die and as a result it's always safe or they die and something else is removing them.. but if some other system is trying to access it before it's removed the system would crash. Adding the barrier of the shared/weak ptr prevents potential race conditions. Could you still safely ensure that everything works in the right order to prevent this from happening? Sure. But that's a lot of mental overhead whereas this rewrite creates safety from within the observer itself. It also signals to the person sending information to the observer that this value might be used by other systems, so make sure you're keeping it safe.
@@ferinzz thank you for expanding the matter. I develop offline applications which receives an input file from a web application, and I couldn’t see these cases. In fact, I manually point my pointer to nullptr after deletion, and hence I can check wether it existists. For legacy reasons, we have our own smart pointer (not so smart at the current standard). Our pointers are incompatible with smart pointers, so I lacked this vision. Thank you again
@@victoroliari9479 Glad i could help add some perspective on the situation here. Making your own smart pointers that work ideally for your own systems makes sense as well! I know Unreal has their own pointer management to reduce the risks of these issues. It's always safe to point things to nullptr when you're done with it to reduce these risks. My understanding as a newbie is that it's easier/safer to start with smart pointers and then optimize as needed since you don't need to worry about the 5 rules if you are using these pointers. Haven't used them myself yet. I'll prob need to start using them on the next iteration of my csv parse. Thanks for sharing.
I generally don't find the need to use shared_ptr. I always use unique_ptr, so it's clear who has ownership of the object. The object can then be passed around as a raw pointer, indicating that it's just meant to be used and no ownership is transferred. I feel that shared ownership just obfuscates the code and is an indication of a design flaw.
Didn't you just introduce an iteration bug by removing the observer in the loop?! You'll be skipping over the next element, when some arbitrary object lifetime expires. That's scary.
WeakPtrs do not modify the reference count and that's why in order to safely use the pointee you need to get a shared pointer if the pointed at object is still alive
I've been coding for about 20 years and used weakptr just once, exactly to break circular dependence on destruction. (Btw it was Python if i remember correctly)
I honestly would have kept the original. Because if the code is designed such that an object can safely be assumed to be alive at some point there's no point in checking it. It's all about scope, if I know a variable will live within a thread I won't use mutex, for instance.
I personally feels that the name of the method `std::shared_ptr::lock()` has little emphasis on what it does. Maybe some names like "rescue" or "sustain" or even "resurrect" would be much more make sense at least to me.
Smart ptrs are designed to document and help enforce the lifetime and ownership of your memory through the compiler. If your codebase uses smart ptrs, you should be consistent and use smart ptrs throughout the codebase unless situation forces a raw ptr to be used or lifetime of the ptr is not exposed publicly and is tightly scoped. Each raw PTR usage is a risk of memory lifetime being misunderstood and uses after free errors Like the Cherno, if you feel that the std smart ptrs are not fit for your uses, make your own
"Each raw PTR usage is a risk of memory lifetime being misunderstood" If you don't understand your lifetimes, then this bandaid is not the fix you want. Fix your lifetimes, then reevalute if you need a shared pointer.
*Summary* This video by The Cherno explains weak pointers in C++ and why they are useful: *What are weak pointers? (**0:58**)* * A C++ class designed to be used with shared pointers. * Provide a non-owning reference to an object managed by shared pointers. * Allow access to an object without extending its lifetime. * Prevent memory leaks caused by circular dependencies between shared pointers. *Why use weak pointers? (**5:40**)* * *Avoid dangling pointers:* Weak pointers provide a safe way to check if the object they point to is still alive. * *Break circular dependencies: (**12:49**)* When two or more objects hold shared pointers to each other, they can't be deleted. Weak pointers prevent this by only one object holding a strong reference. * *Observer pattern:* When an object (observer) needs to monitor another object (subject) without affecting its lifetime. *How to use weak pointers: (**8:38**)* * *Creation:* Created from a shared pointer using `std::weak_ptr weakPtr(sharedPtr);` * *Checking validity:* Use `weakPtr.expired()` to see if the object is alive. * *Accessing the object:* Use `weakPtr.lock()` to obtain a shared pointer to the object. If the object is alive, you can use the returned shared pointer. If not, `lock()` returns a null shared pointer. *Example use case: (**13:50**)* * An input handler keeping track of observer objects. Using weak pointers ensures that if an observer is deleted elsewhere, the input handler won't try to access invalid memory. *Key takeaways:* * Weak pointers are a powerful tool for managing object lifetimes in C++. * They are especially useful in scenarios involving shared ownership and potential circular dependencies. * While adding a slight overhead, they provide increased safety and robustness to your code. i used gemini 1.5 pro to summarize the transcript
You can use them to easily manage memory in a thread safe way. You just have to remember that the assignment to the pointer and the underlying object are not given any thread safety.
If raw pointers are so bad, why there's billions of C and even older C++ software written in the world and written through decades that use them, that worked and some still work and don't constantly crash and burn? What does this say of modern C++ programmers?
Just because it worked, doesn't mean that it is ideal. Sure you can write software with raw pointers, but why? It's much easier and less error prone to use smart pointers. About "What does this say of modern C++ programmers?" Judging people just because they are not making it harder for themself for no reason is a weird take.
"... some still work and don't constantly crash and burn?" - Analysis showed that a large number of bugs in shipped software were caused by double free or access to freed memory. A lot of those bugs are relatively subtle and require unusual/rare circumstances to trigger. Shared pointers are a tool that allows a programmer to make sure his code cannot have bugs of those types, with very little overhead. I don't understand what is wrong with that?
@@pavliv There's plenty of triple A games written in pure C, including full blown OS's, no matter if the software is old or new the basic principles and difficulties are the same.
What do you guys think? How often do you see weak pointers? 👇
Also don’t forget you can try everything Brilliant has to offer-free-for a full 30 days, visit brilliant.org/TheCherno . You’ll also get 20% off an annual premium subscription.
I just see shared pointers actually.
To me there is a time for each type ❤️
I tend to have a preference for encapsulation, so that I know exactly what's going on in memory both in terms of code size and memory allocation. Sometimes the documentation, even for standard libraries, let's admit it, can be a bit obtuse and I often run into the problem where I had to do things the hard way anyway just to understand what the standard is doing, at all. I'm the kinda guy that would be very fastidious about what goes on in the stack, even though I run a system that has an obscene amount of memory for the application set that I run, because it affords you some additional freedom in terms of what kind of optimizations you can implement as far as data access within code that "technically" works just fine, but might have an issue with readability.
I just had a case at work one week ago where changing some shared_ptr to weak_ptr would have solved the immediate problem. Unfortunately, the whole design was flawed and the real solution was to get rid of shared_ptr completely.
In my opinion, real shared ownership is quite rare and most of the time shared_ptr usage is hinting at a design flaw.
I think with Data Oriented Design, you don't need pointers.
use ECS. unique indexes. don't use objects. manage the lifetime of your entities by moving their traits into or out of various arrays of that trait.
Relational databases don't need pointers. neither do any data structure, it can always be done with integer indexes replacing pointers.
Never used it
15:25 removing the observer from the vector while iterating invalidates the current iterator and would not work. You'd need to use the iterator returned from erase() instead of the range-for-loop here.
Unfortunately, it will work, but not quite as expected!
True, nice catch. Even experienced programmers make these silly mistakes and I'm sad no warning is provided by the compiler or static code analysis...
It really does seem like there should be at least a clang-tidy check for stuff like this, but I guess it can be hard to detect when it's several functions deep and across TU borders.
Lol i got told this by chatgpt alot
Yep, and this is true for all programming languages that support list/vector iteration (I think). Need to pay attention to element removal during iteration. Replacing iterator with the return value of erase() is the way to go.
A good case for weak pointer is back referencing. If a class owns another but you want to able to go from the child to the parent, then you can use weak pointers. Something like child1->parent->func(). You know your parent is alive and this way you avoid cyclic dependecies.
I think it's also the only good case
Writing your own smart pointers is probably one of the best learning exercises for C++ in my opinion. Obviously not everyone is going to have a use case for a custom smart pointer implementation, but it teaches you a lot about templating, object lifetime and operator overloading, just to scratch the surface.
This is a excellent observation. I have done this more than once. The ability to write your own smart pointer should be a fundamental skill that one learns when learning C++. Knowing how to do this teaches you RAII, which is useful for many things besides memory management. It also forces you the better understand the overhead of managing memory. If you can write a smart pointer for memory, you can do the same for managing any other resource as well.
Agreed. Writing any sort of RAII class, like a custom data structure, is also a good way to learn about data ownership and lifetimes. Some of my most instructive moments in C++ were when I realized that a strange bug was only present because I hadn't followed the rule of three (or five). It also helps to learn rust, which forces you to think about data ownership
Huge thanks for your C++ series. I learn new stuff every time I watch it.
6:58 "He's dead, Jim." Loved it 😁
"Who's pointer is this?" "It's Zed's" "And where is Zed?" "Zed is dead, baby!"
15:14 Removing an element from a vector while you're currently iterating over said vector will invalidate the reference to the current element in the for loop.
In this case, you're holding the current element by value, so it's not a problem. But if you're holding it as a reference, weird juju happens.
I was wondering about that.
I know C# throws an exception if you try to do this, so I was wondering if C++ had any similar rules/protections for mutating collections with an active iterator.
Unsurprisingly, it seems like "shooting yourself in the foot" is allowed.
Just a note. shared_ptr is not guaranteed to be implemented with reference counting. Therefore use_count might be a linear operation, while expired is always constant.
13:45 I don't like this example. Smart pointers express ownership. In this case, if you can just switch to a weak pointer, then there was no ownership in the first place, and using a shared was an error. That's a case for raw pointers or references. The number places where you need a shared_ptr is massively smaller than the places where people use them.
Good evening Mr. Yan ,
I want to thank you for your outstanding work on the game engine series and on your content on you UA-cam, it was really helpful to me.
One of the series that i really liked was the "Ray Tracing" series and I know that you have said that it's better not to be a series on UA-cam and you didn't want to continue it there but i am asking you to consider continuing it because it's one of the greatest series on your channel ( it talks about graphics more than any other series- game engine series is focused on game engines and c++ series is on c++ , even opengl series we didn't have that much of episodes ).
Thank you so much ❤❤
Have a nice day 🌹
Oh this Shared Pointer rapper makes absolute bangers
Now i can't unhear him saying rapper 😂
"lots of dying going on, welcome to C++" out of context is funny as hell
What about Well-Endowed Pointers?
Explanations are always on point! Thanks for refreshing shared and unique pointers too.
Man, your explanation is really great
thank you
8:20, nitpick :) This is not quite true that we can't know this. We can provide custom deleter for the shared pointer to Object that would store ref to manager and set manager.obj to nullptr shortly before Object is destroyed. The real reason is that this is still not thread safe. The object might be destroyed from a different thread after the if statement returned true. It would need to be synchronised, but we do not reinvent the wheel, cause we already have STL solution, which is std::weak_ptr
I suppose, but a custom deleter means now the Object would have a dependency on the 'manager' class. Object shared pointer has to have 'manager dependent' information (that it has to update its ref and set manager.obj to nullptr). I'd vote against this and stay with std::weak_ptr.
Good explanation, thanks! I will not remove the observer while I'm iterating the list, I guess that invalidates the iterators
Cherno. You are made for educating people. Keep it up!
My man Yan is back, let's keep C++ series rolling back again
Would be good to hear more about your reasoning behind using a custom shared/weak ptr in Hazel?
I loved the weak reference to the Tragedy of Darth Plagueis the Wise.
I'd definitely switch to the safer code in event driven situations like these input observers since they aren't likely to get called thousands of time a second. For anything else, I'd measure and then make that decision.
thanks for having interest in sharing knowledge
Nice neat example ! Great description and explanations.
Be careful though when you call RemoveObserver(observer) inside the for loop over the observers. That may lead to undefined behavior, right ?
Depends on the container used to store observers. For instance, list does not Invalide iterators on remove, so you should be fine. Vector, on the other hand, does invalidate iterators on remove, so this may be UB.
@@antongrant8827 As it is in this case a std::vector ... well
"He's dead Jim."
The guy looks young, but this gave it away 😂
Weird reactions to making the code safer... Personally recently went through a fundamentals course and considering all the overhead that you would normally need to manage (not just knowing lifetime but also overloading stuff) I don't understand why people can't see that this is a good starting point to ensure your code scales safely. Then if there are performance issues (somehow) work on your own better version.
Remember, Java was created to prevent some most of the memory issues that existed before smart pointers were introduced. And a lot of companies moved to Java because of it.
Hello The Cherno, could you make a video on the documentation of our code with Doxygen ?
Would love to see you make a video about C++ Modules
My ex excompany did use them always.
But somehow say ended up using ton of threadlocks and workarounds instead of doing stuff like obj.lock or weakptrs. Guess it has todo with knowledge and design.
In the end it was just threadlocks and ptrs that doesn’t got deleted🎉
Hello guys! That VS theme looks beautiful, could someone share where can I get it? 😮
We want a video on design patterns
great video Can I know what are those themes and plugins cherno is
using (I catched the visual assist one )
The warm lighting in the background is very calming
good one, thank you
I'm little bit new to the c++, but I have question, does auto obj = obj.lock() increment the reference count of the original obj? becuse obj.lock() method returns shared_ptr
Firstly, auto obj = obj.lock() is invalid C++ because you cannot have two objects with the same name (obj). I'm sure that's just a typo, but it's one of the reasons we avoid using variable names that only differ by case (Obj and obj). The Cherno really know better!
Secondly, the return value of weak_ptr::lock() depends on whether weak_ptr::expired() evaluates true or false. If true, the return value is an empty shared_ptr which obviously does not increment any reference counts. If false, the original object is still alive, thus you get a new shared_ptr constructed from the weak_ptr, which naturally increments the reference count.
Note that if you attempt to explicitly construct a shared_ptr from a weak_ptr, an exception is thrown if the weak_ptr has expired. This doesn't happen with lock(), which is an atomic operation.
Surprised the cyclical dep compiled 😮
The only question I would ask is, do you think there's something wrong with the standard pointer types that you would choose not to just use them and instead wrote your own?
Hey @The Cherno , what tool do you use to draw stuff on the screen while teaching, I mean the red markets highlight stuff.
Does anyone knows this? kindly drop a comment
Tell me more about your own reference counting system. Why did you feel the need to make your own?
god i wish i had known this few months ago o.O
Thank you Cherno!
Excellent, time to learn Jai! :o
You can't modify your vector of observers while you're iterating it 😬 been there before!
I like it a lot your explanation. I have a super convenient use case for it. I am making a game where I can shoot among others homing missiles. Those missles hold a weak_ptr to the object they are aiming to. So I dont need to handle the situation when the object gets destroyed (I can more or less just delete it) and the missles can handle the situation then
You can use some sort of entity system, that maps IDs to entities. This way, you would not have to use weak_ptr and instead just hold onto an ID of the target
00:00 wake pointers?
I never really understood weak ptr, till now heh. Might of been better than using shared pointer in some places. Where I couldn't figure out why I had memory leaks and such. I was probably creating things that would never get deleted.
I love it when i compare this to Swift's Automatic Reference Counter. That one does this by default to all memory, and it is pretty slick. Nice to know C++ has it too.
in your example at 13:34, shouldn't B get destroyed first then A ? the output confused me
No, A holds a shared ptr to B, keeping B alive. So A must be destroyed first.
@@michaeldamolsen It makes sense now, A preventing B destructor from now because it owns a shared pointer to it, Thanks for the clarification.
🤔hmmm I've not used shared ptr yet but they are indeed very useful
Is a weak pointer more efficient than doing something like if(pointer), or is it just more safe? I don't really get why people seem to be so against raw pointers, I mean, if something is going to go wrong, make it as loud as possible for easy debugging, lol.
You're actually arguing *for* weak pointers. They're slower, but safer. If you're just holding onto a raw pointer then it is possible that the object was deleted elsewhere. Nothing tells you this. It's also (quite) possible that after the object was deleted it was reallocated as a different type (or even the same type) and that can cause a very difficult to diagnose bug in your code. Using a weak_ptr ensures that your "if(pointer)" (technically the .lock() conversion to a shared_ptr) check always returns null if the object has been deleted. It also ensures that the object remains valid while using it unlike a raw pointer.
So if you're not 100% sure of the lifetime and ownership of an object, it's worth looking into using weak_ptrs. If you are, then unique_ptr is likely a better choice than a raw pointer as it makes ownership explicit. Even when using these types, you're still left deciding which type to use for function parameters (i.e. do you use a raw pointer, a reference, or a const unique_ptr& for a function parameter that use an object stored in a unique_ptr).
@@Ziflinz I do understand what you mean, especially when you're dealing with large code bases, but when you say "nothing tells you if a pointer was deleted elsewhere", that is kind of why I prefer the raw pointer. Because if you get a big juicy error accessing an invalid pointer that hasn't been properly handled, you can then go and setup break points to find the problem. With something setup in a "safe" way, it is not always immediately apparent what the problem is when literally nothing happens, because the pointer is gone and the weak_ptr handles the falloff for you. In my opinion, if a pointer is being deleted when it's not supposed to be, the structure of the code is not ideal, and the sooner you know, the sooner you can restructure.
Hi, I stoped coding cause of my little son. But I always return to keep things in mind while out of c++ world.
Honestly 99% of the time you probably don't even need shared pointers. Organize your code to avoid returning structures which contain arbitrary references. For most tasks, the lifetimes of your data should be pretty obvious.
Except when it isn't
@@-rya1146 Majority of the time it is, there's very few cases where it won't be, mostly in multi-threading or maybe if your consuming someone else's work. (and even then good documentation handles 90% of the latter cases)
This. When I see a shared pointer in your single-threaded code, then I must assume you have no idea of your data flow. Red flag.
Multi-threaded they can come in handy, but if there's a way to structure your flow to not need them, then really just don't use them.
@@Dienes why wouldn't you always design your code to be thread safe?
if it's public, assume someone will use it with data that eventually scales to a million iterations, and assume it will be used asynchronously - because invariably someone will assume your api scales infinitely and is thread safe.
This mentality is exactly why most programs has leaked memory problems, and why people are opting out of Cpp and going for the garbage ass programming language that is rust.
I tend to prefer weak_ptr because, aside from the safety aspect, it adds context over just using *
I'll usually prefer unique_ptr and raw pointers for that project, a ref counted pointer is kinda pointless and wasteful unless you expect your objects to constantly go out of scope without moving their pointer object around or if you're doing multi-threaded work, something which did not happen in that project. I've found that in most single threaded programs its uncommon for me not to know when the object will go out of scope unless I build my project with exceptions.
👍Thanks.
what about templates ?
Hello The Cherno. I submitted an inquiry email a week ago to know more about the engine before becoming a Patreon, and potentially a volunteer. I still didn't get any answers for it.
Swift uses this automatic reference counting system for memory management, it is cool to see the same techniques in cpp
"cyclical shared pointers means the objects can never die"
We're *this* close to hearing you talk about why garbage collection is a good idea, actually!
If an edge case was a reason not to use C++, the language would have died a million deaths.
Cyclical dependencies are more often than not just symptoms of bad design
Removing from the vector while iterating over it... 😬
Don't overuse shared pointers, boys.
Dont use them at all lol
@@literallynull i just used my 400th shared_ptr. what you're gonna do now?
Go for references instead
@@literallynullgood luck with that LMAO
Use raw pointers :))
always kinda strange to peer in on the c++ pointer caste system as a c programmer.
very educational video though ;)
It is have been a year since I found your Chanel and every of your videos is perfect, thank you cherno, can I ask you something
Why your name is Slavic, just want to know.
Surprisingly not pointless
No wont update my code If it works it works , we are doing it raw no protection ....
I've only ever used assembly so I'm totally lost
Please we need some sessions about advanced object-oriented, or to explain about when doing code review
I both like and hate the versatility of cpp...
I think the important thing to remember is smart pointers is for ownership. When your code needs to own some other object on the heap, use smart pointers. If your function someplace just needs to work on a pointed to object (and not save it for later)? Just use raw pointers, call the function with the raw pointer from your smart pointer.
If you need to work without saving it yourself you should be taking a reference to an already existing shared/unique ptr for the duration of your call not taking raw pointers, you're borrowing the ownership of your caller and guarenteeing that you wont accidentally do something stupid with a raw pointer
@@TurtleKwitty The idea is fine, but it does not work fully. You make a function that does something with some type. you don't want a copy so you take it by pointer. Ahh but you heard raw pointers are evil, so you use a smart pointer and take it by ref because you don't actually want the ownership. But there are two smart pointer so now you need to make two overloads, and it only works for object contained in a smart pointer. We might still need to be able to work with stack allocated objects
I think the reference is a good idea, but then do it on the contained type. Or make a non_owning pointer wrapper to be explicit about it being non owning if you don't like raw pointers. Like non_owning or something.
@@TheTykbry just take the red to the stack what the fuck xD not using raw pointers doesn't mean you randomly throw things into shared ptr for no reason XD
@@TurtleKwitty yes you don't. Exactly. But you suggested taking the smart pointer by ref, now forcing the user to do exactly that. So you should just take the parameter of what the smart pointer points to, lets say a struct S. So your function could just look like (foo(S& s) or foo(S* s), and then you would call it with a smart pointer: foo(*s_ptr.get()) for calling the ref version or foo(s_ptr.get()) for the pointer version.
And now foo even works for non smart pointer types. Compared to what you suggested originally by taking the smart pointer by ref, somehting like this: void foo(std::shared_ptr& s), forcing every user to use a smart pointer. Got a local S or some other kind of stack bases S? Or maybe unique_ptr? Too bad, put that in a shard_ptr!
@@TheTykbry Ref yes, pointer no. The entire point is to eliminate possible foot guns, which converting back to a pointer would not do. So yes if your structure isn't always on the heap ref to it directly, if you always have a pointer ref to it directly. Your original post was about handling cases where functions take things by raw pointers to nit have the ownership overhead so the answer to the specific thing you brought up is red to the smart pointer. But yes as a general rule if you're doing things from scratch use refs everywhere possible
What if it’s a unique_ptr and not a shared_ptr🤔
Unique pointer has the sole ownership of an object. When the object goes out of scope, the unique pointer cleans it up. This means you can't copy a unique pointer, thus making it not-unique.
A shared pointer has a shared ownership of an object. Every time the shared pointer is copied, the objects reference count increases, and every time a copy goes out of scope, the reference count of the object decreases. Only when the objects ref count hits zero is the object cleaned up. This prevents null pointer undefined behavior.
I think in all my C++ code I've ever written there was only a single case where shared and weap ptr would have been useful, but I used a raw ptr instead, because the cyclic reference was managed by the 2 cyclic classes being instantiated only once as RAII objects not as heap allocated memory. I will keep them in mind though.
Personally, I am a fan of raw pointers. Probably because when I started to work at my current company, I inherited a project to maintain that was full of std::shared_ptr. Most of them were unnecessary or incorrectly used. There was a case of a singleton that kept on dying because implementation was depended on a std::shared_ptr living throughout the program's live span. If it died, the next call to instance would create a new one and you ended up with total weird behavior that was very very very hard to track down. It was very satisfying when I did though.
Ah, the upgrade of global variables: obscured global variables of inconsistent volatility.
... I'm not sure the shared_ptr is the problem here.
You all should be using your own memory pool utility.
For me it sounds like that you MUST use smart pointers ALWAYS until you know that it's usage in exact place cause NOTICEABLE overhead.
First make it safe. Optimize later.
Yep, that's a typical case of premature optimization. It's compounded by the fact that C++ and Rust make the simple, safe case far more awkward to write.
How long did you wait to do the initial joke ? Feels like you prepared your whole life for it :D
I feel like you’ve solved a problem that wasn’t a problem.
Is it all just to save a 4 bits integer value on stack? In the example, the pointed object was deleted, but there was still an integer storing the adress. Why bother using weak pointer instead of raw pointers?
I’ll watch it again.
The problem is that if the original pointer gets deleted, the memory it points to becomes invalid. So if you try to use your pointer to call a method or whatever, the application is going to crash because the address that your pointer is pointing to no longer contains the object.
the observer doesn't know (and shouldn't know) anything about the lifetime of the pointers it's holding onto. There was no other safety from invalid pointers during iteration so either they never die and as a result it's always safe or they die and something else is removing them.. but if some other system is trying to access it before it's removed the system would crash.
Adding the barrier of the shared/weak ptr prevents potential race conditions.
Could you still safely ensure that everything works in the right order to prevent this from happening? Sure. But that's a lot of mental overhead whereas this rewrite creates safety from within the observer itself.
It also signals to the person sending information to the observer that this value might be used by other systems, so make sure you're keeping it safe.
@@ferinzz thank you for expanding the matter. I develop offline applications which receives an input file from a web application, and I couldn’t see these cases. In fact, I manually point my pointer to nullptr after deletion, and hence I can check wether it existists.
For legacy reasons, we have our own smart pointer (not so smart at the current standard). Our pointers are incompatible with smart pointers, so I lacked this vision.
Thank you again
@@victoroliari9479 Glad i could help add some perspective on the situation here.
Making your own smart pointers that work ideally for your own systems makes sense as well! I know Unreal has their own pointer management to reduce the risks of these issues.
It's always safe to point things to nullptr when you're done with it to reduce these risks. My understanding as a newbie is that it's easier/safer to start with smart pointers and then optimize as needed since you don't need to worry about the 5 rules if you are using these pointers.
Haven't used them myself yet. I'll prob need to start using them on the next iteration of my csv parse.
Thanks for sharing.
I generally don't find the need to use shared_ptr. I always use unique_ptr, so it's clear who has ownership of the object. The object can then be passed around as a raw pointer, indicating that it's just meant to be used and no ownership is transferred. I feel that shared ownership just obfuscates the code and is an indication of a design flaw.
3:36 "the powder" .. yeah sure .. take less cocaine Yan and take care of your family! :P
That’s why I use C. Pointers in C++ are pretty weak
hahaha nice one.
Just use it as C inside C++.
Soy pointer. :3
Didn't you just introduce an iteration bug by removing the observer in the loop?! You'll be skipping over the next element, when some arbitrary object lifetime expires. That's scary.
Can you tell me please which text editor are you using?
Microsoft Visual Studio (the full Visual Studio, not Visual Studio Code).
@@JavedAlam-ce4mu Thank you : )
WeakPtrs do not modify the reference count and that's why in order to safely use the pointee you need to get a shared pointer if the pointed at object is still alive
It modifies the weak count not the owning count but it does track weak count
I've been coding for about 20 years and used weakptr just once, exactly to break circular dependence on destruction. (Btw it was Python if i remember correctly)
I honestly would have kept the original. Because if the code is designed such that an object can safely be assumed to be alive at some point there's no point in checking it. It's all about scope, if I know a variable will live within a thread I won't use mutex, for instance.
just change the extension to .c and call free, boom! problem solved!🤣
those pointers are just like you: weak :D
I am a weak pointer
pretty weak
I personally feels that the name of the method `std::shared_ptr::lock()` has little emphasis on what it does.
Maybe some names like "rescue" or "sustain" or even "resurrect" would be much more make sense at least to me.
The term is in line with other concurrency classes of the standard, and it certainly makes more sense than "resurrect"
just use go
I always use new and delete. What is even smart pointer
Smart ptrs are designed to document and help enforce the lifetime and ownership of your memory through the compiler.
If your codebase uses smart ptrs, you should be consistent and use smart ptrs throughout the codebase unless situation forces a raw ptr to be used or lifetime of the ptr is not exposed publicly and is tightly scoped. Each raw PTR usage is a risk of memory lifetime being misunderstood and uses after free errors
Like the Cherno, if you feel that the std smart ptrs are not fit for your uses, make your own
"Each raw PTR usage is a risk of memory lifetime being misunderstood"
If you don't understand your lifetimes, then this bandaid is not the fix you want. Fix your lifetimes, then reevalute if you need a shared pointer.
It's a great time to start learning Rust 😅
Good luck with that
*Summary*
This video by The Cherno explains weak pointers in C++ and why they are useful:
*What are weak pointers? (**0:58**)*
* A C++ class designed to be used with shared pointers.
* Provide a non-owning reference to an object managed by shared pointers.
* Allow access to an object without extending its lifetime.
* Prevent memory leaks caused by circular dependencies between shared pointers.
*Why use weak pointers? (**5:40**)*
* *Avoid dangling pointers:* Weak pointers provide a safe way to check if the object they point to is still alive.
* *Break circular dependencies: (**12:49**)* When two or more objects hold shared pointers to each other, they can't be deleted. Weak pointers prevent this by only one object holding a strong reference.
* *Observer pattern:* When an object (observer) needs to monitor another object (subject) without affecting its lifetime.
*How to use weak pointers: (**8:38**)*
* *Creation:* Created from a shared pointer using `std::weak_ptr weakPtr(sharedPtr);`
* *Checking validity:* Use `weakPtr.expired()` to see if the object is alive.
* *Accessing the object:* Use `weakPtr.lock()` to obtain a shared pointer to the object. If the object is alive, you can use the returned shared pointer. If not, `lock()` returns a null shared pointer.
*Example use case: (**13:50**)*
* An input handler keeping track of observer objects. Using weak pointers ensures that if an observer is deleted elsewhere, the input handler won't try to access invalid memory.
*Key takeaways:*
* Weak pointers are a powerful tool for managing object lifetimes in C++.
* They are especially useful in scenarios involving shared ownership and potential circular dependencies.
* While adding a slight overhead, they provide increased safety and robustness to your code.
i used gemini 1.5 pro to summarize the transcript
hey that's nice!
Does it "watch" the video or read subtitles?
Atomics are pretty expensive though, right? Cache invalidation hurts
First non-bot comment
yeah, I've been noticing a ton of bots lately
I still just use raw ptrs, all these fancy ptr types are just silly
Don't use smart pointers. If you have memory leak it's just a skill issue.
Be careful, std::shared_ptr is not multithread safe
The reference increments and decrements are thread safe. The managed object data isn't.
Saying vague things doesn't help much.
You can use them to easily manage memory in a thread safe way.
You just have to remember that the assignment to the pointer and the underlying object are not given any thread safety.
your example is just bad practice :D it will never happen in the real world when you are coding for good :) it will not pass the code review :))
If raw pointers are so bad, why there's billions of C and even older C++ software written in the world and written through decades that use them, that worked and some still work and don't constantly crash and burn?
What does this say of modern C++ programmers?
Different levels of abstraction. Try coding some modern triple A game in pure C and you will get the idea
Just because it worked, doesn't mean that it is ideal. Sure you can write software with raw pointers, but why? It's much easier and less error prone to use smart pointers.
About "What does this say of modern C++ programmers?"
Judging people just because they are not making it harder for themself for no reason is a weird take.
good point. but why stop there? if writing code by individually punching holes in cards is so bad,
"... some still work and don't constantly crash and burn?"
- Analysis showed that a large number of bugs in shipped software were caused by double free or access to freed memory. A lot of those bugs are relatively subtle and require unusual/rare circumstances to trigger. Shared pointers are a tool that allows a programmer to make sure his code cannot have bugs of those types, with very little overhead. I don't understand what is wrong with that?
@@pavliv There's plenty of triple A games written in pure C, including full blown OS's, no matter if the software is old or new the basic principles and difficulties are the same.