The Real Price of Shared Pointers in C++
CC Attribution - NonCommercial - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Josuttis, Nicolai M.
Shared pointers play a fundamental part of modern C++ programming. However, they come with a price. The price usually known is the overhead of the control object, which can became even more expensive if make_shared() is used. But there is an additional price that can play an important role if good performance is key: synchronization. As a consequence counter-intuitive things such as passing shared pointers by reference can become important. This talk discusses the whole story of shared pointers.
 I think we are ready to start. Welcome to NDC and especially to the C++ track. I'm happy a lot of people are here for C++ or maybe just for looking about shared pointers or so. My name is Nikolaj Yosotis. I give the first talk in this track today. I give later another talk, but this talk is about using shared pointers in C++. I'm happy to get a rough idea of the people who are sitting here in this room. We also have people remotely connected. So who is an active programmer in C++? Almost all. Who is using shared pointers all the time? That's a few. Okay. So maybe you know afterwards why. You don't use them or you use them. Okay. My name is Nikolaj Yosotis. You might have heard of the name in different areas. I was an M part of the standardization committee of the informal members there working on the standard for almost 20 years now. But I have other areas where I work on like service oriented architecture, privacy, etc. And what we are talking about is about C++. C++ has different versions. The first version of 19.8 had a bug fix in 03. That's the C++ that's most widely used still right now. And in C++ 11, we came up with an update, the new C++ version. And they are officially for the first time shared pointers were introduced. However, we had them earlier in boost as a library you could use. And by the way, this C++ 11 new major version of C++ 11 now will have additional versions. So last year C++ 14 was finished, which is just which just has only some minor updates. And every three years we will have further standards. That's the plan so far. So the next major version will be C++ 17. And we have a couple of smart pointers in the standard. Now there were only one smart pointer in the past standard, which was auto pointer, which you should not use anymore. Auto pointer is in some places broken. It was replaced now by unique pointer. Unique pointers have the idea of having exclusive ownership at one time in the program. The resource can be only held by one owner, by one pointer. And that's the implementation there. You can hand over this ownership to somebody else. That's where auto pointer is broken. It's not correctly implemented there. So unique pointer fixes this. But we are talking about shared ownership. Shared ownership means that we have a smart pointer sharing a resource or that we have multiple shared pointers, multiple pointers sharing the resource. And they are smart in the sense that they help you not to make mistakes. So that means they are smarter than just ordinary raw pointers in the way that, for example, automatically when the last owner of the resource doesn't go away, or says, I don't need this resource anymore, then this is the time where the destructor of the resource gets deleted or gets removed, whatever it is. Okay. Let's look a bit into the details. By the way, this you can consider as one implementation of a garbage collection approach like in Java, but specific for specific objects, not for types in general or for the language as a whole, but just for specific objects in that you say, if somebody creates a resource, we keep it as long as the last owner who needs it exists. So let's look at an example. I have here a shared pointer GP declared. And then I create a new resource. I allocate it on the heap with new. This is just a raw pointer and dealing with this raw pointer. Yeah. Sooner or later, we have to delete the resource or we might want to delete the resource with delete. However, I might hand over this resource to different places in the program who want to deal with this resource. So instead of passing around raw pointers and trying to program logically that I do the right thing, I will hand this raw pointer over to a shared pointer. And this pointer then takes control of the resource. You should of course give up the control via raw pointer then because otherwise you can screw up things under the hood of this shared pointer. Therefore, usually the first two statements are just one statement where you pass new C as an argument. Here you see a first information which you might or might not be aware of. You pay a price to manage the objects and the price, the first price is you need to control objects because to manage your resource and all the owners of the resource, somebody has to count them because somebody somewhere we have to find out that the last owner doesn't exist anymore. And therefore when I hand over a raw pointer referring to resource to a shared pointer, then there will be a call internally allocating a control object. This on control object will be initialized by the initial number of owners. That's one in this case. Now, when I assign this shared pointer to another place, to another object, to another owner who also wants to have access to this resource, like in this assigned statement, then this other resource also becomes an owner. And as we centrally have to manage the number of owners, this will lead to the fact that in the control object, the number of owners will get incremented. So we have now both SP and NGP using the resource, but they both own the resource. And the idea is if either one of them give up the control, the number of owners will get decremented, but if the last owner disappears, then the resource will be freed. By the way, the next two statements, we print the so-called use count. You can print out how many owners we have at a certain time. So let's leave the block where SP is declared, which means SP disappears the initial owner of this resource. So we now have only GP as one owner of the resource. And now when we leave the block, when we print the use count, it will be one. And now if we leave GP, then the resource will automatically be deleted. What is called when we delete or when the last owner gives up control of the ownership, or control of the resource gives up ownership, can be configured. By default, we call delete. So the default is appropriate if you create something with new, but you can define different things. Here you see a first example on the first line where you can say, for example, the initial share pointer is initialized by the return value of a previous owner. The return value of a P open, that is, we open a pipe. The return type is a pointer to fire. So it's a shared pointer sharing this reference to the opened pipe. And then when we can define what should happen when we free the resource or when we delete the resource, when the last owner gives up ownership, what we define here is we say, well, then we close the pipe, we print out that we close the pipe, and then we close it. What you see here is a so-called lambda. It's a new piece of functionality, a new feature of C++11. It's an inline defined function. So here you define a function which takes as an argument the raw pointer, the initial raw pointer, and you define what deletion shall do here. You can instead pass ordinary pointers if you want, but this is probably more convenient because together with the initial initialization, together with the P open, we see the P close, the corresponding P close to give up the ownership here. And during the lifetime, you can use a shared pointer as before, hand it around, having different owners, being able to use this pipe to read or write there. There are other things you can define, for example, if you allocate memory with new as an array, then calling delete, the normal delete is not appropriate. For example, the next example, you allocate 10 integers, the corresponding deleter should call to delete an area of integers, which by the way, there are different ways to do that. You can also use a helper function calling default delete with an argument saying this is an area of integers. But if you forget to define this deleter, you simply make an error because you allocated an area, but you delete only one element, so there might be a problem. But probably not within, but if this is an area of your own type of strings or of customers or so. With C++ 17, we will also give another opportunity to say here as a type of shared pointer to say this is a shared pointer for integer arrays, which will then internally automatically call the deleter for arrays. Good. This is the basic knowledge, the basic title. We have something that shares resources. We have an ownership counter. We can hand ownership around. If the last owner disappears, resource gets freed and all deleted, and we can define what deletion does, is for. That's basic. Now let's add one more piece after the other. The first thing to add is weak pointers. We might have a problem with shared pointer, a general problem which you might have if you have reference counting and garbage collection, which means cyclic references to each other. So if the last owner gets deleted, then this means that this can become a problem if we have a situation like here. Resource A has internally a shared pointer referring to resource B, and resource B has internally a shared pointer referring to resource A. I initially created resource A with one shared pointer and resource B with another shared pointer from the outside, and then I programmed the handshake. So even if I remove the two shared pointers to the outside, both resources will refer to each other, so there will be an owner. And as there will be an owner, these resources won't be given up, won't be deleted. So we have to break this cycle. And one way to do it is to use a so-called weak pointer. A weak pointer is sharing but does not count as an owner. So in this place, for example, resource A has one owner only, the shared pointer on the top left, but resource B has two owners, shared pointer on the top right and resource A. And then when we delete the outer pointer on the top left, resource A will have no longer any owner, will be deleted, and because if that's deleted, then we will delete resource B if it's not any more used by the shared pointer on the top right. So this is for cyclic references. This is also for situations where we want to observe pointers but that we don't want to count as owners. So sometimes from a programming logic, you want to have the situation that internally we use something, we use some resource, but it might be deleted, but we still want to observe it from the outside, so therefore weak pointers are for. Here's how you use them. A weak pointer you declare as a shared pointer, and then you can assign a shared pointer to a weak pointer, like the assignment statement on the top. So for example, you create a new C here, initialize a shared pointer with a new C, call a member for it, and then you can assign this shared pointer to a weak pointer, referring also to the object, but not counting as owner. This weak pointer is not correct, is not exactly a smart pointer in a way that you can access your resource directly using a pointer interface. You have to do something a little bit more. Here are three ways to do it. So the first thing is you have to call lock. So if you have a weak pointer and you want to access your resource, call lock, then lock creates a shared pointer, but only provided this resource still exists. If not, you can check that. Then this shared pointer does not own anything, which is why we have an if here. So before we access our member via the locked weak pointer, we check whether the result in shared pointer is fine. We can directly ask whether the weak pointer expired, and if not, we know that lock is successful, and we can access the member. Well, unless we have multi-threading application where between checking for exploration and locking somebody might give up ownership, that's a different issue. We come to that. And the third way to deal with weak pointers is we can initialize a shared pointer by a weak pointer, and if the shared pointer does not own, is a weak pointer, does not refer to the object. It never owns, but if it does not refer to the resource, so the last owner disappeared already, then this will throw an exception. The last thing is safe and racist because the first two examples mean that we have two calls that belong together. So in multi-threading environments, we would need a protected region between lock and checking or between expired and lock. Nothing, there we got an exception if it no longer exists. So how does it work? In practice, internally, let's create a weak pointer here. Let's create a shared pointer, both not referring to any resource. Let's create another shared pointer which owns a new resource just created. So as said before, the shared pointer has a control object counting the owners, but here's a more secret revealed that the control object not only counts the owners, they also have to count the weak owners. So because when we now assign a shared pointer to another shared pointer, we have two owners but no weak owners, then we assign this to a weak pointer, and then we have one more weak pointer. So we have now three people using, three people, three smart pointers using the resource, two counters owners, one not. And now let's remove or let's give up control via the first shared pointer. Let's give up control via the second shared pointer. That means we lose our last owner. That's this situation. This is a situation where the resource gets deleted, but we still need the control object because the weak pointer is able to find out whether this object still exists or not. It's smarter than a raw pointer in the sense that it finds out when it dangles, when it refers to a resource that no longer exists. So by keeping the control block alive, when we use the weak pointer, for example, call lock or ask are you expired, we know the answer because the weak pointer still use the central control object for all the smart pointers using this resource. So there's an overhead for weak pointers or in general there's an overhead for shared and weak pointers, which is we have two counters in the global heap counting the owners and the weak pointers. So that's the real price of shared pointers regarding size. Well, it turned out that it might be a little bit expensive always to call new for the resource and then allocate on the heap the control object. That's two news together. Two allocations on the heap. Heap allocation is one of the most expensive calls you might have in your program, so there's a new optimization which was not initially part of boost but now is in the standard and now it's also boost, which is called make shared. Make shared combines the call for new for the resource with a call of new for the control block. So let's see what this means. You call make shared. Well, first of all, we have a global shared pointer. We call make shared and now we allocate at once the memory of the resource plus the memory for the control object counting, by the way, both owners and weak pointers. I don't have weak pointers here as a member there listed. It's still the case that we count weak pointers here. By the way, if you see the red statement here, here's something new in C++11. This is a place where you can use auto to declare your shared pointer. So forget about this detailed type here. It's just what makes shared returns as a shared pointer referring to this resource. And just, well, in case you find this more convenient, you don't have to. And by the way, this also has another advantage because the control object no longer needs a pointer to the resource. So we save some memory here. Again, if we have a second shared pointer we assign, then the owner gets increased. And then if we leave the scope so that the inner shared pointer disappears, then we have only one owner. And now if we lose the last owner, the whole resource will be deleted, including calling the destructor or the deleter function we have for the resource. By the way, I said calling the deleter function. One problem here is make shared cannot be used to define your own deleter function. So this can only be used for new and delete for this combination. So that's also a drawback. What happens if we have weak pointers? Well, with weak pointers it's a little bit more complicated. You might say, oh, then we have the same issue as with weak pointers, but there's one drawback here. And the drawback is the following. Guess we have an outer weak pointer, and the next inner statement is a shared pointer, and then we have a third state shared pointer which is initialized by make shared. Then we have here the resource and the control both allocated together with one call to the heap. Which means that we can only free them together. We can't free half of the allocated memory here. So that has a following consequence. We assign this to another shared pointer. We assign this to weak pointer. We leave the scope of one shared pointer. We leave the scope of a second shared pointer. And now the resource gets disrupted, but not the memory for the resource released. So that means you pay a price for make shared in a way that if you have observers to your resource, then the memory for your resource will remain longer than necessary, which can be a significant issue if you are in an embedded system, and memory is the limit in your system. So you have a resource that I don't know, wastes one megabyte of data. The resource does not exist anymore, but because there's an observer observing your resource, the weak pointer, this memory is not freed. It is freed until the last weak pointer is deleted, because we allocated that together. So be careful, it's not always better to call make shared. Think about that. Okay, what else do we have in functionality of shared pointers? Sometimes you have the problem that you want to, that you need a shared pointer for an object without having the existing shared pointer owning an object. So you create an object, you assign it to a shared pointer, and sooner or later, for example, inside your object, one of your member functions, so, oh, I would like to have a reference to myself, or I would like somebody else to give a shared pointer for me as an object. The object does not know the shared pointer, the object does only know itself. But it's important that if you as an object give others control to you, why a shared pointer, that they share the same control object. So you can't just give another shared pointer your address again, like this, because then we have two conflicting controls of our object. So this can be solved, there's some support for that, by deriving your type from a class called enable shared from this. You pass yourself as an argument, it's a template class, and the effect is as follows. If you have your resource, and if you create your resource with new, then this resource will not only have your members, it will also have a member derived from the base class, and this derived member from the base class has the idea to refer to the shared pointer in case there is one. So far there isn't one, so this member will be initialized by the null pointer, a new keyboard we have in C++, it's just a pointer not referring to anywhere. So now when we give control for this object to a shared pointer, the following happens. It's not only that for the shared pointer we create the control object with the owner count and the recount, also the object itself gets the knowledge that this control object exists. So the member we derived from the base class enabled shared from this now refers to this control object. And that can be used in a way that we call a member function of the resource. So using the shared pointer we call a member function called getPTR, getPTR is implemented in the class itself, so in the class we don't know, we need control or we need access, we want to return the shared pointer to this object, and this is done by shared from this. So when you derive from an enabled shared from this, you also derive not only a member, but also a helper function shared from this, and by using it now we return a second shared pointer using the same control object. This technique might help you, this technique is not perfect. Deriving from base classes with enabled shared from this creates some interesting problems which I will not show you in detail when you have multiple base classes. So class here with multiple base class and the question is where and which class has as a base enabled shared from this technique, etc. So there are limits to that or there are some drawbacks to that. But in principle if you just have your class or your base class of your system and you sometimes need shared pointers to your objects without copying or existing shared pointers, this technique might help you. And there's even more to know about the functionality of shared pointers. Here's another example. On the left you see a class hierarchy, class person, and class customers derived from class person. This hierarchy for normal pointers creates the ability to have an automatic conversion from derived class to base class and to be able to derive for pointers to downcast persons to customers. So because we can store in an area of person pointers we can store customers. This has gone away because a shared pointer is a template and a template initialized by any type is a very distinct type. Inheritance relations are not transferred to templates using these types having the inheritance transaction. For that reason we have some special operations here. For example, when you deal with shared pointers, instead of using dynamic cast to downcast you have to use dynamic pointer cast, which is a special member function for shared pointers which ensures that the dynamic cast checks whether this is valid, whether there's really an inheritance hierarchy, and you have to use them. Without them it will not compile. That's all. That's all you should know about shared pointer functionality, whether there's more, but that's what I want to tell here because now we should talk about the price of shared pointers. What we have talked about the price already, you see there's a control object. This control object has two members, the number of owners, the number of weak pointers, and a reference to the pointer unless you create it with make shared. But that's not all. Just some numbers. The size of the control object usually has 16 to 20 bytes. It does not only store the bebers, it also stores the deleter in case it's not trivia. So not always, but sometimes it needs additional space for the deleter. So we have an overhead of 20 bytes in general for each resource shared by different shared pointers, which might be an issue for you. If you use make shared, you save, usually save the pointer to your resource. All these numbers are not guaranteed. It's just the way it is implemented in the compilers. But usually you save four bytes because you skip the pointer to your resource because we know that right after the control object, the resource is. And if we derive from a base class having a pointer to shared from this, we add typically another eight bytes for this. I don't know exactly why. It's a pointer and probably some information about the type or about the state or what so ever. So that's a size overhead, control object and the size overhead. So let's talk about another problem. And the other problem is how about using shared pointer in different threads? Guess for example, I have here a shared pointer globally defined. And now I have two threads in blue here on the left and on the right. And in one thread, I push this shared pointer into a vector of shared pointers, which creates a copy of this shared pointer. And on the right side, I call a helper function process passing the shared pointer by value, which also creates a copy. Passing by value is creating copy. So we have two threads now simultaneously create a new owner, an additional owner. The situation is as follows. In the middle, we have our global shared pointer. We have the resource. We have the control object having only one owner I skip week here. Thread A, thread B. So what we do is one thread called process, which creates a copy. Another thread called push back, which creates a copy. Both will create a new shared pointer and have to increment the owner, the number of owners, which is a concurrent write access from two different threads to the counter for the owners, which is a problem, which has to get synchronized. Otherwise, we will have some strange situations like half written data or only after even two increments. There's still the numbers only two instead of three. So we have to synchronize this access and the question. And by the way, if we have, if we have, and by the way, we do that in C++. So we decided that this is allowed that here the owner counter is an atomic counter or some technique like that saying different, different shared, different threads can, that it's guaranteed if different threads make a copy, create a new owner or an ownership, some, an owner gets deleted. So ownership gets decremented. This will be synchronized. We have the same problem even worse a little bit when we have owners and weak pointers. Because think about the following situation you see here. I have a shared pointer and a weak pointer. In one thread, we have, we delete the shared pointer. In the other thread, we delete the weak pointer. So at the same time, both objects get deleted. Who gives up the memory of the resource? The first thread will set owner to zero. The second thread will set weeks to zero. The first set will say, oh, there's still a weak pointer. And the second set will say, oh, there's still a shared pointer. So it's not enough here just to have owner counts and weak counts atomic. We might even synchronize the combined access to owner and weak over different threads, which is pretty expensive, which means instead of using atomics, which on a lot of systems are natively supported, we would need locks and unlocks because we have two resources to lock. But there's a solution for that. The solution is that strictly speaking inside this control object, we don't have an owner and a weak counter. The second counter counts owners and weeks. So we have an owner counter and we have an owner counter and we have a counter for owners and weeks so that we have only one member. If this goes to zero, then we free the resource. So we can deal with atomics here. By the way, there's another trick saying, well, we don't want to reduce weeks all the time, so we make it a little bit different, the implementation. Initially, we initialize weeks with one and then with every new weak pointer, we increment it. But with every new owner, we don't increment it. So weak is always one plus the number of weak pointers. And then when we delete the owners, we only decrement owners unless we are the last owner. And if the last owner gets deleted, we also decrement weak so that we limit the access to another atomic object. Like weak counter. But if this object becomes zero, it means there's no other owner because there's a one, if there's any other owner plus the number of weak pointers. So that's a typical implementation to make it optimal. See, it's a little bit tricky, even this simple example to make it performing. So to say, I give you some insights how it is implemented, we can guarantee this situation. So this is guaranteed that this is okay, which is not guaranteed is access, shared access in other way to the shared pointer. So it's not okay in one thread to reset this shared pointer, to assign something new, or in another thread to query the use count. This is a read access to the shared pointer. This is the write access to a shared pointer. The guarantee that shared access is given is only regarding the use count of the object, not regarding in general access to the shared pointer. So be careful there. If you want to have multiple threads dealing in multiple ways with shared pointer, so one thread assigning a new value, another thread asking for the U count, you need something different. We have concurrent access to shared pointers we have in C++11, some global helper functions which are very dangerous to use because you everywhere have to use this atomic functions. If you forgot it, at one place, everything is screwed up. So this is replaced by a new approach in C++17, or will be replaced by a new class atomic shared pointer so that we have a type that ensures that each and every access to a shared pointer is atomic, not only an access that has impact on the use count. Good. What we have seen is dealing with problems like concurrent read and write access. What we have seen here is that we can solve this problem by using atomic counters as members. These atomic counters are often in the platforms natively there, and in just often but not always atomic in your CPUs, and sometimes but not always even increment and decrement operations for this native atomic type is atomic. However, the C++ standard guarantees if you declare something as an atomic, like here I declare something as an atomic bool ready, then in our case it's atomic int, then increment and decrement is an atomic operation. So are we done with the problem? No, we are not. We have another problem. And the other problem is we have the problem of reordered statements, or we have the feature of reordered statements. I want to explain this a little bit to you. This is how memories, how CPUs work these days. It's just one example and it's a very brief example. Guess for example, and it's oversimplified, but to give you just some idea. Guess we have here some memory, and I want to modify a resource, then the memory has to get loaded into the CPU in a cache, and then we can modify it. And if another CPU needs the same memory, they have to send a message to the other CPU, say I need now this resource, please invalidate the resource for you, give it to me, and I can increment this value. This is protocol running around in CPUs with multiple cores. The problem is CPU one uses this memory, CPU zero wants to use this memory, it takes some time until it gets a memory from CPU one. This is what we call a writeStall. I'm going to write to this memory, but I have to wait until I have access to this memory, because it's used somewhere else in another CPU. WriteStalls are evil, because that means your CPU does nothing. So what they do, they cache write statements. So they have something like a full saw buffer, and that leads to the following situation. Look at the example on the bottom here. On the left, you have a provider setting a value to one, and then signaling ready is true for a variable used by visible for both CPUs. So multiple threads running here. On the right side, we have a loop waiting until ready is true, and then consuming the A. But the problem is here, currently in this case, that for whatever reason, CPU zero already has ready in the buffer, in the cache, CPU one already has A in the cache. So what happens? On the right side, we ask, we want to check ready. So we ask, can you send us ready? Why on the left side, we said A to one. This is a write access, but we don't want to wait until we have access to A, so we write it in a buffer. So, and we said then ready to true. Now something interesting happens, which is, oh, I have access to ready, oh, then I said ready to true, while still waiting for A to get access to A. What we have here, in fact, is a re-order statement. We first said ready to true, and wait for setting A to one. So the answer will be ready is true. And on the right side, the loop is done. We have ready to, but unfortunately, we don't have the new value of A, because we have re-ordered statements here. This is a very, very important optimization technique in modern CPUs. And it's enabled by default. You have to disable B if this is a problem. It's not violating the C++ rule that you have, that this is detectable different behavior. The rule saying any optimization is forbidden, which you can detect from the outside, only applies to one single thread. It does not apply to multiple threads. And by the way, note here, this is not an optimization programmed by the compiler. It's an optimization done by and inside your CPU. CPUs re-order statements if they think this has no effect to the outside. Because looking from this, from CPU zero, nobody uses A and ready in this thread, so it doesn't matter which I update first. So, yeah, we need something here called memory barriers. So what we need here is to say, hey, in this case, it's not okay to re-order statement. We can do that. We can tell the CPU, no, it's not allowed to re-order the statements here. So when we do that, for example, here, saying we have SMP write barrier, then re-ordering is not allowed so that when we read for ready, there is a write barrier saying before you get ready, before we set ready to true, somebody has to write A. So we don't update ready immediately. Ready is false, it's returned. And then we get access to A, and once we get access to A, we update A, then we update ready, and then the ready becomes true, and on the right side, we have the write value, we have access to the A, and we have the write value. Memory barriers make possessors slower. That's the point, because this is an optimization technique that usually brings a lot of speed, an incredible amount of speed, and we have exactly the same problem in shared pointers, so we need some memory barriers for counters and for weak pointers, which, by the way, makes our program slower, because we disable re-ordering there. By the way, we only have these write barriers, we also have read barriers in another way, but that's detailed. So when we use atomic operation, the C++ standard guarantees that each atomic operation is a memory barrier. So when I use a data type like atomic bool, or for our counters, atomic int, the C++ standard will say, it's not allowed that data equals 42 will happen after I store ready to true. That's a guarantee in the standard, and this guarantee is given in a way that we say, okay, each atomic assess is a memory barrier, so create such a low-level call. There is an opportunity to skip that. The atomic interface of C++ allows to say, no, it's not a memory barrier, or it's only a memory barrier in some circumstances. For example, here I say, it's okay, it's not okay to move date, the assignment to data after assigning true to ready, but it would be okay to re-order an assignment after store in front of it. So some re-ordering are allowed, which aren't critical, some not. And same on the other side by saying I, with memory order acquire here, I say it's not allowed to move read access that happens behind in front of this, but it's okay to move read access that happens to the front after this loop, so that we only have limited barriers, which we create in the assembler code of the CPUs. By the way, this is something for real experts, where we really care about this little piece of software, but it's important for shared pointers, it pays a higher price than you would have thought. So let's see the effect of this on shared pointers. Here's a program having 130 million iterations, and these 130 million iterations might or might not copy a shared pointer. In fact, we have a function here called pass shared pointer, where we optionally can turn on or turn off passing by value or passing by reference. Let's run this program. I'm able to run this program in different modes. The first, I can say this program runs on, I can say how many threads this program runs on, and I can say, shall it use value semantics or reference semantics? So let's start by saying shared PTR path, using one thread only, and passing all the values by value, all the shared pointers by value. So what it means, we make 130 million copies of a shared void pointer. You see? You saw how long it took? Well, you didn't measure, but you got an impression. Let's now, instead of passing shared pointers by value, pass them by reference. Same program, just passing the shared pointers by reference. It's a little bit faster, a little bit. So there's already an effect, although we only have one CPU used here. Now let's do the same with four CPUs. So let's use four CPUs, well, just to compare using one CPU passing by value, using four CPUs passing by value. Same number of shared pointers are copied. You see how much slower this is. And now let's use four CPUs, instead of passing them by value, passing by reference. This is huge. This is really huge. If you have a program where you say, well, let's use shared pointer everywhere, garbage collection, I don't have to think about ownership. Everything comes for free, which is not the case, by the way, because there are other problems. You have to be sure that sometimes the leaders are called, and that you don't waste resources, you no longer need. But you see also a performance price here, and the performance is significant. And this is only because of synchronized access and memory barriers. We already have these cost of memory barriers. We even have a price in one CPU, if only one thread is used. By the way, in Boost there's now an ability to optimize shared pointers in a way that when you declare them, can say this is only needed in one thread, so no need to synchronize the access, so that they skip the code for synchronization. Of course, you have a problem if you then use a shared pointer and multiple threads. But this is not standardized. It's only in Boost. So, yes, that's the whole story. So that means, here we have also the same results. We have a factor between 30 and 40 in this program. With optimizations, we have a factor up to 180. And we use that, and one customer of mine used it in a new database, using a huge number of threads, and because of that effect, they changed their guidelines, saying whenever you pass a shared pointer in a function, pass it by reference, not by value. Well, be careful. You can argue that it's okay. Is a reference more dangerous than a value? Well, yes, but it's not that bad. Look at this example. We have here, on the bottom, we have a vector of shared pointers. We have a function bar. Bar takes a shared pointer, maybe by reference, maybe not. So if I take it by reference, and I just use it, and I make sure that in the outside, while I call bar, the shared pointer remains valid, then everything is fine. If internally I make a new copy, for example, pushing it into a vector, this is by value operation. But passing the shared pointer by reference from foo to bar is fine, as long as we ensure that the shared pointer exists in foo as long as bar exists. Which, by the way, is the case. If the shared pointer we pass to bar is on the stack. So passing stack objects by reference is safe. Passing heap objects by reference is a problem. It can be a problem. So here's another example. It's a pretty indirect example. I don't think we have enough time to go into detail, but the rough idea is we get in bar a shared pointer, and then inside this indirectly called, we delete the initial owner of this object accidentally. So while we are in bar, we delete our own object owning this shared pointer. So that's, of course, a problem. There's a workaround for that. We could create, before we call bar, we could create a shared pointer on the stack. So the company who told me about that example said, they have a style guide saying, always, if you have a stack object, pass shared pointers always by reference. And if you have heap objects, also pass them by reference. But please create on the stack a copy of the shared pointer first. Because then we are safe. Good. That's it. We have an overhead. We have a significant, we have an overhead in size. We have a significant overhead in time and performance. So you might, for example, consider passing shared pointers by reference. Well, however, some of you might say, there's this famous guy called Herb Sutter, who wrote Guru of the Week. And in Guru of the Week 91, there's an interesting article about, shall we use shared pointer, or when shall we use shared pointer and when not? This article discusses whether in a function, I should pass a shared pointer by value or by reference. I should use a unique pointer instead by value or by reference. Or I should even use a raw pointer instead. I don't go through the basics here, but in principle, he recommends in a lot of places to use raw pointers instead of shared pointers. I'm not happy about that. I have a different opinion. You can discuss this. But I want to point out that what he recommends, everything is fast except passing shared pointer by value. So everything else is fast. When we just talk about performance, and they're as equal fast as they are similarly fast, I can show you in the example, I can show you this because I also have implemented to pass a raw pointer instead. This is passing a reference, a shared pointer reference, now passing a raw pointer. It's almost the same. You see here the output is screwed up because multiple threads print output here. That's leading to not synchronized output. It's okay, but you have mixed output. So that means I don't want to discuss here. That's a different discussion, whether raw pointer are better than using shared pointers when you pass them in a function. But I definitely can say if you pass them by value, there might be some drawback. Oops. So, beside that, in my opinion, there must be a good, very good reason not to use shared pointers and to use raw pointers. I don't see the reason here because whenever you pass a raw pointer, there's a danger that somebody else creates a new shared pointer from it having an additional control object. But that's, as I said, a different discussion. I just wanted to make clear what is the real price of shared pointer. I think you have seen it. And so you might think about some styleguides for that reason to pass shared pointers by reference more than before. That's it. Thank you very much. I'm here around until Friday. Feel free to ask me. In the afternoon, I give a talk about the interesting story how we deal dealt with no accept in the standard. And by the way, you just heard the talk by Bruce Schneier in the morning. We have to think about how we can deal with privacy. I'm a contributor to Enigmail, which is the email add-on in Mozilla Thunderbird so that you can send emails encrypted. I'm working for, there for free. We all work for for free, only two guys, unfortunately. And, but we have to at least have, we have a website, which we run, so feel free to help us come and test the program or to donate. That's totally different thing, but maybe after the keynote it's worth saying it. Thank you very much.
 I think we are ready to start.  Welcome to NDC and especially to the C++ track.  I'm happy a lot of people are here for C++ or maybe just for looking about shared pointers or so.  My name is Nikolaj Yosotis. I give the first talk in this track today.  I give later another talk, but this talk is about using shared pointers in C++.  I'm happy to get a rough idea of the people who are sitting here in this room.  We also have people remotely connected.  So who is an active programmer in C++?  Almost all. Who is using shared pointers all the time?  That's a few. Okay.  So maybe you know afterwards why. You don't use them or you use them.  Okay. My name is Nikolaj Yosotis.  You might have heard of the name in different areas.  I was an M part of the standardization committee of the informal members there working on the standard for almost 20 years now.  But I have other areas where I work on like service oriented architecture, privacy, etc.  And what we are talking about is about C++. C++ has different versions.  The first version of 19.8 had a bug fix in 03.  That's the C++ that's most widely used still right now.  And in C++ 11, we came up with an update, the new C++ version.  And they are officially for the first time shared pointers were introduced.  However, we had them earlier in boost as a library you could use.  And by the way, this C++ 11 new major version of C++ 11 now will have additional versions.  So last year C++ 14 was finished, which is just which just has only some minor updates.  And every three years we will have further standards.  That's the plan so far. So the next major version will be C++ 17.  And we have a couple of smart pointers in the standard.  Now there were only one smart pointer in the past standard, which was auto pointer, which you should not use anymore.  Auto pointer is in some places broken.  It was replaced now by unique pointer.  Unique pointers have the idea of having exclusive ownership at one time in the program.  The resource can be only held by one owner, by one pointer.  And that's the implementation there.  You can hand over this ownership to somebody else.  That's where auto pointer is broken.  It's not correctly implemented there.  So unique pointer fixes this.  But we are talking about shared ownership.  Shared ownership means that we have a smart pointer sharing a resource or that we have multiple shared pointers, multiple pointers sharing the resource.  And they are smart in the sense that they help you not to make mistakes.  So that means they are smarter than just ordinary raw pointers in the way that, for example, automatically when the last owner of the resource doesn't go away,  or says, I don't need this resource anymore, then this is the time where the destructor of the resource gets deleted or gets removed, whatever it is.  Okay.  Let's look a bit into the details.  By the way, this you can consider as one implementation of a garbage collection approach like in Java, but specific for specific objects,  not for types in general or for the language as a whole, but just for specific objects in that you say, if somebody creates a resource,  we keep it as long as the last owner who needs it exists.  So let's look at an example.  I have here a shared pointer GP declared.  And then I create a new resource.  I allocate it on the heap with new.  This is just a raw pointer and dealing with this raw pointer.  Yeah.  Sooner or later, we have to delete the resource or we might want to delete the resource with delete.  However, I might hand over this resource to different places in the program who want to deal with this resource.  So instead of passing around raw pointers and trying to program logically that I do the right thing, I will hand this raw pointer over to a shared pointer.  And this pointer then takes control of the resource.  You should of course give up the control via raw pointer then because otherwise you can screw up things under the hood of this shared pointer.  Therefore, usually the first two statements are just one statement where you pass new C as an argument.  Here you see a first information which you might or might not be aware of.  You pay a price to manage the objects and the price, the first price is you need to control objects because to manage your resource and all the owners of the resource,  somebody has to count them because somebody somewhere we have to find out that the last owner doesn't exist anymore.  And therefore when I hand over a raw pointer referring to resource to a shared pointer, then there will be a call internally allocating a control object.  This on control object will be initialized by the initial number of owners.  That's one in this case.  Now, when I assign this shared pointer to another place, to another object, to another owner who also wants to have access to this resource,  like in this assigned statement, then this other resource also becomes an owner.  And as we centrally have to manage the number of owners, this will lead to the fact that in the control object, the number of owners will get incremented.  So we have now both SP and NGP using the resource, but they both own the resource.  And the idea is if either one of them give up the control, the number of owners will get decremented, but if the last owner disappears, then the resource will be freed.  By the way, the next two statements, we print the so-called use count.  You can print out how many owners we have at a certain time.  So let's leave the block where SP is declared, which means SP disappears the initial owner of this resource.  So we now have only GP as one owner of the resource.  And now when we leave the block, when we print the use count, it will be one.  And now if we leave GP, then the resource will automatically be deleted.  What is called when we delete or when the last owner gives up control of the ownership, or control of the resource gives up ownership, can be configured.  By default, we call delete.  So the default is appropriate if you create something with new, but you can define different things.  Here you see a first example on the first line where you can say, for example, the initial share pointer is initialized by the return value of a previous owner.  The return value of a P open, that is, we open a pipe.  The return type is a pointer to fire.  So it's a shared pointer sharing this reference to the opened pipe.  And then when we can define what should happen when we free the resource or when we delete the resource, when the last owner gives up ownership,  what we define here is we say, well, then we close the pipe, we print out that we close the pipe, and then we close it.  What you see here is a so-called lambda.  It's a new piece of functionality, a new feature of C++11.  It's an inline defined function.  So here you define a function which takes as an argument the raw pointer, the initial raw pointer,  and you define what deletion shall do here.  You can instead pass ordinary pointers if you want, but this is probably more convenient because together with the initial initialization,  together with the P open, we see the P close, the corresponding P close to give up the ownership here.  And during the lifetime, you can use a shared pointer as before, hand it around, having different owners, being able to use this pipe to read or write there.  There are other things you can define, for example, if you allocate memory with new as an array, then calling delete,  the normal delete is not appropriate.  For example, the next example, you allocate 10 integers, the corresponding deleter should call to delete an area of integers,  which by the way, there are different ways to do that.  You can also use a helper function calling default delete with an argument saying this is an area of integers.  But if you forget to define this deleter, you simply make an error because you allocated an area,  but you delete only one element, so there might be a problem.  But probably not within, but if this is an area of your own type of strings or of customers or so.  With C++ 17, we will also give another opportunity to say here as a type of shared pointer to say this is a shared pointer for integer arrays,  which will then internally automatically call the deleter for arrays.  Good. This is the basic knowledge, the basic title.  We have something that shares resources.  We have an ownership counter.  We can hand ownership around.  If the last owner disappears, resource gets freed and all deleted, and we can define what deletion does, is for.  That's basic.  Now let's add one more piece after the other.  The first thing to add is weak pointers.  We might have a problem with shared pointer, a general problem which you might have if you have reference counting and garbage collection,  which means cyclic references to each other.  So if the last owner gets deleted, then this means that this can become a problem if we have a situation like here.  Resource A has internally a shared pointer referring to resource B, and resource B has internally a shared pointer referring to resource A.  I initially created resource A with one shared pointer and resource B with another shared pointer from the outside,  and then I programmed the handshake.  So even if I remove the two shared pointers to the outside, both resources will refer to each other, so there will be an owner.  And as there will be an owner, these resources won't be given up, won't be deleted.  So we have to break this cycle.  And one way to do it is to use a so-called weak pointer.  A weak pointer is sharing but does not count as an owner.  So in this place, for example, resource A has one owner only, the shared pointer on the top left,  but resource B has two owners, shared pointer on the top right and resource A.  And then when we delete the outer pointer on the top left, resource A will have no longer any owner, will be deleted,  and because if that's deleted, then we will delete resource B if it's not any more used by the shared pointer on the top right.  So this is for cyclic references.  This is also for situations where we want to observe pointers but that we don't want to count as owners.  So sometimes from a programming logic, you want to have the situation that internally we use something,  we use some resource, but it might be deleted, but we still want to observe it from the outside,  so therefore weak pointers are for.  Here's how you use them.  A weak pointer you declare as a shared pointer, and then you can assign a shared pointer to a weak pointer,  like the assignment statement on the top.  So for example, you create a new C here, initialize a shared pointer with a new C, call a member for it,  and then you can assign this shared pointer to a weak pointer, referring also to the object, but not counting as owner.  This weak pointer is not correct, is not exactly a smart pointer in a way that you can access your resource directly using a pointer interface.  You have to do something a little bit more.  Here are three ways to do it.  So the first thing is you have to call lock.  So if you have a weak pointer and you want to access your resource, call lock, then lock creates a shared pointer,  but only provided this resource still exists.  If not, you can check that.  Then this shared pointer does not own anything, which is why we have an if here.  So before we access our member via the locked weak pointer, we check whether the result in shared pointer is fine.  We can directly ask whether the weak pointer expired, and if not, we know that lock is successful,  and we can access the member.  Well, unless we have multi-threading application where between checking for exploration and locking somebody might give up ownership,  that's a different issue.  We come to that.  And the third way to deal with weak pointers is we can initialize a shared pointer by a weak pointer,  and if the shared pointer does not own, is a weak pointer, does not refer to the object.  It never owns, but if it does not refer to the resource, so the last owner disappeared already,  then this will throw an exception.  The last thing is safe and racist because the first two examples mean that we have two calls that belong together.  So in multi-threading environments, we would need a protected region between lock and checking or between expired and lock.  Nothing, there we got an exception if it no longer exists.  So how does it work?  In practice, internally, let's create a weak pointer here.  Let's create a shared pointer, both not referring to any resource.  Let's create another shared pointer which owns a new resource just created.  So as said before, the shared pointer has a control object counting the owners,  but here's a more secret revealed that the control object not only counts the owners,  they also have to count the weak owners.  So because when we now assign a shared pointer to another shared pointer,  we have two owners but no weak owners, then we assign this to a weak pointer,  and then we have one more weak pointer.  So we have now three people using, three people, three smart pointers using the resource,  two counters owners, one not.  And now let's remove or let's give up control via the first shared pointer.  Let's give up control via the second shared pointer.  That means we lose our last owner.  That's this situation.  This is a situation where the resource gets deleted,  but we still need the control object because the weak pointer is able to find out  whether this object still exists or not.  It's smarter than a raw pointer in the sense that it finds out when it dangles,  when it refers to a resource that no longer exists.  So by keeping the control block alive, when we use the weak pointer, for example,  call lock or ask are you expired, we know the answer because the weak pointer still  use the central control object for all the smart pointers using this resource.  So there's an overhead for weak pointers or in general there's an overhead for shared  and weak pointers, which is we have two counters in the global heap counting the  owners and the weak pointers.  So that's the real price of shared pointers regarding size.  Well, it turned out that it might be a little bit expensive always to call new  for the resource and then allocate on the heap the control object.  That's two news together.  Two allocations on the heap.  Heap allocation is one of the most expensive calls you might have in your program,  so there's a new optimization which was not initially part of boost but now is  in the standard and now it's also boost, which is called make shared.  Make shared combines the call for new for the resource with a call of new for the  control block.  So let's see what this means.  You call make shared.  Well, first of all, we have a global shared pointer.  We call make shared and now we allocate at once the memory of the resource plus the  memory for the control object counting, by the way, both owners and weak pointers.  I don't have weak pointers here as a member there listed.  It's still the case that we count weak pointers here.  By the way, if you see the red statement here, here's something new in C++11.  This is a place where you can use auto to declare your shared pointer.  So forget about this detailed type here.  It's just what makes shared returns as a shared pointer referring to this resource.  And just, well, in case you find this more convenient, you don't have to.  And by the way, this also has another advantage because the control object no longer needs  a pointer to the resource.  So we save some memory here.  Again, if we have a second shared pointer we assign, then the owner gets increased.  And then if we leave the scope so that the inner shared pointer disappears, then we  have only one owner.  And now if we lose the last owner, the whole resource will be deleted, including  calling the destructor or the deleter function we have for the resource.  By the way, I said calling the deleter function.  One problem here is make shared cannot be used to define your own deleter function.  So this can only be used for new and delete for this combination.  So that's also a drawback.  What happens if we have weak pointers?  Well, with weak pointers it's a little bit more complicated.  You might say, oh, then we have the same issue as with weak pointers, but there's one drawback here.  And the drawback is the following.  Guess we have an outer weak pointer, and the next inner statement is a shared pointer,  and then we have a third state shared pointer which is initialized by make shared.  Then we have here the resource and the control both allocated together with one call to the heap.  Which means that we can only free them together.  We can't free half of the allocated memory here.  So that has a following consequence.  We assign this to another shared pointer.  We assign this to weak pointer.  We leave the scope of one shared pointer.  We leave the scope of a second shared pointer.  And now the resource gets disrupted, but not the memory for the resource released.  So that means you pay a price for make shared in a way that if you have observers to your resource,  then the memory for your resource will remain longer than necessary,  which can be a significant issue if you are in an embedded system, and memory is the limit in your system.  So you have a resource that I don't know, wastes one megabyte of data.  The resource does not exist anymore, but because there's an observer observing your resource,  the weak pointer, this memory is not freed.  It is freed until the last weak pointer is deleted, because we allocated that together.  So be careful, it's not always better to call make shared. Think about that.  Okay, what else do we have in functionality of shared pointers?  Sometimes you have the problem that you want to, that you need a shared pointer for an object  without having the existing shared pointer owning an object.  So you create an object, you assign it to a shared pointer,  and sooner or later, for example, inside your object, one of your member functions,  so, oh, I would like to have a reference to myself, or I would like somebody else to give a shared pointer for me as an object.  The object does not know the shared pointer, the object does only know itself.  But it's important that if you as an object give others control to you,  why a shared pointer, that they share the same control object.  So you can't just give another shared pointer your address again, like this,  because then we have two conflicting controls of our object.  So this can be solved, there's some support for that, by deriving your type from a class called enable shared from this.  You pass yourself as an argument, it's a template class, and the effect is as follows.  If you have your resource, and if you create your resource with new,  then this resource will not only have your members, it will also have a member derived from the base class,  and this derived member from the base class has the idea to refer to the shared pointer in case there is one.  So far there isn't one, so this member will be initialized by the null pointer,  a new keyboard we have in C++, it's just a pointer not referring to anywhere.  So now when we give control for this object to a shared pointer, the following happens.  It's not only that for the shared pointer we create the control object with the owner count and the recount,  also the object itself gets the knowledge that this control object exists.  So the member we derived from the base class enabled shared from this now refers to this control object.  And that can be used in a way that we call a member function of the resource.  So using the shared pointer we call a member function called getPTR,  getPTR is implemented in the class itself, so in the class we don't know,  we need control or we need access, we want to return the shared pointer to this object,  and this is done by shared from this.  So when you derive from an enabled shared from this, you also derive not only a member,  but also a helper function shared from this, and by using it now we return a second shared pointer  using the same control object.  This technique might help you, this technique is not perfect.  Deriving from base classes with enabled shared from this creates some interesting problems  which I will not show you in detail when you have multiple base classes.  So class here with multiple base class and the question is where and which class has as a base enabled shared from this technique, etc.  So there are limits to that or there are some drawbacks to that.  But in principle if you just have your class or your base class of your system  and you sometimes need shared pointers to your objects without copying or existing shared pointers,  this technique might help you.  And there's even more to know about the functionality of shared pointers.  Here's another example.  On the left you see a class hierarchy, class person, and class customers derived from class person.  This hierarchy for normal pointers creates the ability to have an automatic conversion from derived class to base class  and to be able to derive for pointers to downcast persons to customers.  So because we can store in an area of person pointers we can store customers.  This has gone away because a shared pointer is a template and a template initialized by any type is a very distinct type.  Inheritance relations are not transferred to templates using these types having the inheritance transaction.  For that reason we have some special operations here. For example, when you deal with shared pointers,  instead of using dynamic cast to downcast you have to use dynamic pointer cast,  which is a special member function for shared pointers which ensures that the dynamic cast checks whether this is valid,  whether there's really an inheritance hierarchy, and you have to use them.  Without them it will not compile.  That's all. That's all you should know about shared pointer functionality, whether there's more,  but that's what I want to tell here because now we should talk about the price of shared pointers.  What we have talked about the price already, you see there's a control object.  This control object has two members, the number of owners, the number of weak pointers,  and a reference to the pointer unless you create it with make shared.  But that's not all.  Just some numbers. The size of the control object usually has 16 to 20 bytes.  It does not only store the bebers, it also stores the deleter in case it's not trivia.  So not always, but sometimes it needs additional space for the deleter.  So we have an overhead of 20 bytes in general for each resource shared by different shared pointers,  which might be an issue for you.  If you use make shared, you save, usually save the pointer to your resource.  All these numbers are not guaranteed.  It's just the way it is implemented in the compilers.  But usually you save four bytes because you skip the pointer to your resource  because we know that right after the control object, the resource is.  And if we derive from a base class having a pointer to shared from this,  we add typically another eight bytes for this.  I don't know exactly why.  It's a pointer and probably some information about the type or about the state or what so ever.  So that's a size overhead, control object and the size overhead.  So let's talk about another problem.  And the other problem is how about using shared pointer in different threads?  Guess for example, I have here a shared pointer globally defined.  And now I have two threads in blue here on the left and on the right.  And in one thread, I push this shared pointer into a vector of shared pointers,  which creates a copy of this shared pointer.  And on the right side, I call a helper function process passing the shared pointer by value,  which also creates a copy.  Passing by value is creating copy.  So we have two threads now simultaneously create a new owner, an additional owner.  The situation is as follows.  In the middle, we have our global shared pointer.  We have the resource.  We have the control object having only one owner I skip week here.  Thread A, thread B.  So what we do is one thread called process, which creates a copy.  Another thread called push back, which creates a copy.  Both will create a new shared pointer and have to increment the owner, the number of owners,  which is a concurrent write access from two different threads to the counter for the owners,  which is a problem, which has to get synchronized.  Otherwise, we will have some strange situations like half written data or only after even two increments.  There's still the numbers only two instead of three.  So we have to synchronize this access and the question.  And by the way, if we have, if we have, and by the way, we do that in C++.  So we decided that this is allowed that here the owner counter is an atomic counter or some  technique like that saying different, different shared, different threads can,  that it's guaranteed if different threads make a copy, create a new owner or an ownership,  some, an owner gets deleted.  So ownership gets decremented.  This will be synchronized.  We have the same problem even worse a little bit when we have owners and weak pointers.  Because think about the following situation you see here.  I have a shared pointer and a weak pointer.  In one thread, we have, we delete the shared pointer.  In the other thread, we delete the weak pointer.  So at the same time, both objects get deleted.  Who gives up the memory of the resource?  The first thread will set owner to zero.  The second thread will set weeks to zero.  The first set will say, oh, there's still a weak pointer.  And the second set will say, oh, there's still a shared pointer.  So it's not enough here just to have owner counts and weak counts atomic.  We might even synchronize the combined access to owner and weak over different threads,  which is pretty expensive, which means instead of using atomics, which on a lot of systems are natively supported,  we would need locks and unlocks because we have two resources to lock.  But there's a solution for that.  The solution is that strictly speaking inside this control object,  we don't have an owner and a weak counter.  The second counter counts owners and weeks.  So we have an owner counter and we have an owner counter and we have a counter for owners and weeks  so that we have only one member.  If this goes to zero, then we free the resource.  So we can deal with atomics here.  By the way, there's another trick saying, well, we don't want to reduce weeks all the time,  so we make it a little bit different, the implementation.  Initially, we initialize weeks with one and then with every new weak pointer, we increment it.  But with every new owner, we don't increment it.  So weak is always one plus the number of weak pointers.  And then when we delete the owners, we only decrement owners unless we are the last owner.  And if the last owner gets deleted, we also decrement weak so that we limit the access to another atomic object.  Like weak counter.  But if this object becomes zero, it means there's no other owner because there's a one,  if there's any other owner plus the number of weak pointers.  So that's a typical implementation to make it optimal.  See, it's a little bit tricky, even this simple example to make it performing.  So to say, I give you some insights how it is implemented, we can guarantee this situation.  So this is guaranteed that this is okay, which is not guaranteed is access,  shared access in other way to the shared pointer.  So it's not okay in one thread to reset this shared pointer, to assign something new,  or in another thread to query the use count.  This is a read access to the shared pointer.  This is the write access to a shared pointer.  The guarantee that shared access is given is only regarding the use count of the object,  not regarding in general access to the shared pointer.  So be careful there.  If you want to have multiple threads dealing in multiple ways with shared pointer,  so one thread assigning a new value, another thread asking for the U count,  you need something different.  We have concurrent access to shared pointers we have in C++11,  some global helper functions which are very dangerous to use  because you everywhere have to use this atomic functions.  If you forgot it, at one place, everything is screwed up.  So this is replaced by a new approach in C++17,  or will be replaced by a new class atomic shared pointer  so that we have a type that ensures that each and every access to a shared pointer is atomic,  not only an access that has impact on the use count.  Good.  What we have seen is dealing with problems like concurrent read and write access.  What we have seen here is that we can solve this problem by using atomic counters as members.  These atomic counters are often in the platforms natively there,  and in just often but not always atomic in your CPUs,  and sometimes but not always even increment and decrement operations for this native atomic type is atomic.  However, the C++ standard guarantees if you declare something as an atomic,  like here I declare something as an atomic bool ready,  then in our case it's atomic int,  then increment and decrement is an atomic operation.  So are we done with the problem?  No, we are not.  We have another problem.  And the other problem is we have the problem of reordered statements,  or we have the feature of reordered statements.  I want to explain this a little bit to you.  This is how memories, how CPUs work these days.  It's just one example and it's a very brief example.  Guess for example, and it's oversimplified, but to give you just some idea.  Guess we have here some memory,  and I want to modify a resource,  then the memory has to get loaded into the CPU in a cache,  and then we can modify it.  And if another CPU needs the same memory,  they have to send a message to the other CPU,  say I need now this resource,  please invalidate the resource for you,  give it to me, and I can increment this value.  This is protocol running around in CPUs with multiple cores.  The problem is CPU one uses this memory,  CPU zero wants to use this memory,  it takes some time until it gets a memory from CPU one.  This is what we call a writeStall.  I'm going to write to this memory,  but I have to wait until I have access to this memory,  because it's used somewhere else in another CPU.  WriteStalls are evil,  because that means your CPU does nothing.  So what they do, they cache write statements.  So they have something like a full saw buffer,  and that leads to the following situation.  Look at the example on the bottom here.  On the left, you have a provider setting a value to one,  and then signaling ready is true for a variable  used by visible for both CPUs.  So multiple threads running here.  On the right side, we have a loop waiting  until ready is true, and then consuming the A.  But the problem is here,  currently in this case, that for whatever reason,  CPU zero already has ready in the buffer, in the cache,  CPU one already has A in the cache.  So what happens?  On the right side, we ask, we want to check ready.  So we ask, can you send us ready?  Why on the left side, we said A to one.  This is a write access, but we don't want to wait  until we have access to A, so we write it in a buffer.  So, and we said then ready to true.  Now something interesting happens, which is,  oh, I have access to ready,  oh, then I said ready to true,  while still waiting for A to get access to A.  What we have here, in fact, is a re-order statement.  We first said ready to true,  and wait for setting A to one.  So the answer will be ready is true.  And on the right side, the loop is done.  We have ready to, but unfortunately,  we don't have the new value of A,  because we have re-ordered statements here.  This is a very, very important optimization technique  in modern CPUs.  And it's enabled by default.  You have to disable B if this is a problem.  It's not violating the C++ rule that you have,  that this is detectable different behavior.  The rule saying any optimization is forbidden,  which you can detect from the outside,  only applies to one single thread.  It does not apply to multiple threads.  And by the way, note here,  this is not an optimization programmed by the compiler.  It's an optimization done by and inside your CPU.  CPUs re-order statements if they think this has no effect to the outside.  Because looking from this, from CPU zero,  nobody uses A and ready in this thread,  so it doesn't matter which I update first.  So, yeah, we need something here called memory barriers.  So what we need here is to say, hey, in this case,  it's not okay to re-order statement.  We can do that.  We can tell the CPU, no, it's not allowed to re-order the statements here.  So when we do that, for example, here,  saying we have SMP write barrier,  then re-ordering is not allowed so that when we read for ready,  there is a write barrier saying before you get ready,  before we set ready to true, somebody has to write A.  So we don't update ready immediately.  Ready is false, it's returned.  And then we get access to A,  and once we get access to A, we update A,  then we update ready, and then the ready becomes true,  and on the right side, we have the write value,  we have access to the A, and we have the write value.  Memory barriers make possessors slower.  That's the point, because this is an optimization technique  that usually brings a lot of speed,  an incredible amount of speed,  and we have exactly the same problem in shared pointers,  so we need some memory barriers for counters and for weak pointers,  which, by the way, makes our program slower,  because we disable re-ordering there.  By the way, we only have these write barriers,  we also have read barriers in another way, but that's detailed.  So when we use atomic operation,  the C++ standard guarantees that each atomic operation is a memory barrier.  So when I use a data type like atomic bool,  or for our counters, atomic int,  the C++ standard will say,  it's not allowed that data equals 42 will happen after I store ready to true.  That's a guarantee in the standard,  and this guarantee is given in a way that we say,  okay, each atomic assess is a memory barrier,  so create such a low-level call.  There is an opportunity to skip that.  The atomic interface of C++ allows to say,  no, it's not a memory barrier,  or it's only a memory barrier in some circumstances.  For example, here I say, it's okay,  it's not okay to move date, the assignment to data after assigning true to ready,  but it would be okay to re-order an assignment after store in front of it.  So some re-ordering are allowed, which aren't critical, some not.  And same on the other side by saying I, with memory order acquire here,  I say it's not allowed to move read access that happens behind in front of this,  but it's okay to move read access that happens to the front after this loop,  so that we only have limited barriers,  which we create in the assembler code of the CPUs.  By the way, this is something for real experts,  where we really care about this little piece of software,  but it's important for shared pointers,  it pays a higher price than you would have thought.  So let's see the effect of this on shared pointers.  Here's a program having 130 million iterations,  and these 130 million iterations might or might not copy a shared pointer.  In fact, we have a function here called pass shared pointer,  where we optionally can turn on or turn off passing by value or passing by reference.  Let's run this program.  I'm able to run this program in different modes.  The first, I can say this program runs on,  I can say how many threads this program runs on,  and I can say, shall it use value semantics or reference semantics?  So let's start by saying shared PTR path,  using one thread only, and passing all the values by value,  all the shared pointers by value.  So what it means, we make 130 million copies of a shared void pointer.  You see? You saw how long it took?  Well, you didn't measure, but you got an impression.  Let's now, instead of passing shared pointers by value,  pass them by reference.  Same program, just passing the shared pointers by reference.  It's a little bit faster, a little bit.  So there's already an effect, although we only have one CPU used here.  Now let's do the same with four CPUs.  So let's use four CPUs, well,  just to compare using one CPU passing by value,  using four CPUs passing by value.  Same number of shared pointers are copied.  You see how much slower this is.  And now let's use four CPUs,  instead of passing them by value, passing by reference.  This is huge. This is really huge.  If you have a program where you say,  well, let's use shared pointer everywhere, garbage collection,  I don't have to think about ownership.  Everything comes for free, which is not the case, by the way,  because there are other problems.  You have to be sure that sometimes the leaders are called,  and that you don't waste resources, you no longer need.  But you see also a performance price here,  and the performance is significant.  And this is only because of synchronized access and memory barriers.  We already have these cost of memory barriers.  We even have a price in one CPU, if only one thread is used.  By the way, in Boost there's now an ability to optimize shared pointers  in a way that when you declare them,  can say this is only needed in one thread,  so no need to synchronize the access,  so that they skip the code for synchronization.  Of course, you have a problem if you then use a shared pointer  and multiple threads.  But this is not standardized. It's only in Boost.  So, yes, that's the whole story.  So that means, here we have also the same results.  We have a factor between 30 and 40 in this program.  With optimizations, we have a factor up to 180.  And we use that, and one customer of mine used it in a new database,  using a huge number of threads,  and because of that effect, they changed their guidelines,  saying whenever you pass a shared pointer in a function,  pass it by reference, not by value.  Well, be careful.  You can argue that it's okay.  Is a reference more dangerous than a value?  Well, yes, but it's not that bad.  Look at this example.  We have here, on the bottom, we have a vector of shared pointers.  We have a function bar.  Bar takes a shared pointer, maybe by reference, maybe not.  So if I take it by reference, and I just use it,  and I make sure that in the outside, while I call bar,  the shared pointer remains valid, then everything is fine.  If internally I make a new copy, for example, pushing it into a vector,  this is by value operation.  But passing the shared pointer by reference from foo to bar is fine,  as long as we ensure that the shared pointer exists in foo as long as bar exists.  Which, by the way, is the case.  If the shared pointer we pass to bar is on the stack.  So passing stack objects by reference is safe.  Passing heap objects by reference is a problem.  It can be a problem.  So here's another example.  It's a pretty indirect example.  I don't think we have enough time to go into detail,  but the rough idea is we get in bar a shared pointer,  and then inside this indirectly called,  we delete the initial owner of this object accidentally.  So while we are in bar, we delete our own object owning this shared pointer.  So that's, of course, a problem.  There's a workaround for that.  We could create, before we call bar, we could create a shared pointer on the stack.  So the company who told me about that example said,  they have a style guide saying,  always, if you have a stack object,  pass shared pointers always by reference.  And if you have heap objects, also pass them by reference.  But please create on the stack a copy of the shared pointer first.  Because then we are safe.  Good. That's it.  We have an overhead.  We have a significant, we have an overhead in size.  We have a significant overhead in time and performance.  So you might, for example,  consider passing shared pointers by reference.  Well, however, some of you might say,  there's this famous guy called Herb Sutter,  who wrote Guru of the Week.  And in Guru of the Week 91,  there's an interesting article about,  shall we use shared pointer,  or when shall we use shared pointer and when not?  This article discusses whether in a function,  I should pass a shared pointer by value or by reference.  I should use a unique pointer instead by value or by reference.  Or I should even use a raw pointer instead.  I don't go through the basics here,  but in principle, he recommends in a lot of places  to use raw pointers instead of shared pointers.  I'm not happy about that.  I have a different opinion.  You can discuss this.  But I want to point out that what he recommends,  everything is fast except passing shared pointer by value.  So everything else is fast.  When we just talk about performance,  and they're as equal fast as they are similarly fast,  I can show you in the example,  I can show you this because I also have implemented  to pass a raw pointer instead.  This is passing a reference, a shared pointer reference,  now passing a raw pointer.  It's almost the same.  You see here the output is screwed up  because multiple threads print output here.  That's leading to not synchronized output.  It's okay, but you have mixed output.  So that means I don't want to discuss here.  That's a different discussion,  whether raw pointer are better than using shared pointers  when you pass them in a function.  But I definitely can say if you pass them by value,  there might be some drawback.  Oops.  So, beside that, in my opinion,  there must be a good, very good reason not to use shared pointers  and to use raw pointers.  I don't see the reason here  because whenever you pass a raw pointer,  there's a danger that somebody else creates a new shared pointer  from it having an additional control object.  But that's, as I said, a different discussion.  I just wanted to make clear what is the real price of shared pointer.  I think you have seen it.  And so you might think about some styleguides  for that reason to pass shared pointers by reference more than before.  That's it. Thank you very much.  I'm here around until Friday.  Feel free to ask me.  In the afternoon, I give a talk about the interesting story  how we deal dealt with no accept in the standard.  And by the way, you just heard the talk by Bruce Schneier in the morning.  We have to think about how we can deal with privacy.  I'm a contributor to Enigmail,  which is the email add-on in Mozilla Thunderbird  so that you can send emails encrypted.  I'm working for, there for free.  We all work for for free, only two guys, unfortunately.  And, but we have to at least have, we have a website,  which we run, so feel free to help us come and test the program  or to donate.  That's totally different thing,  but maybe after the keynote it's worth saying it.  Thank you very much.
The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_0_1379.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_1380_1979.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_1980_3455.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_3456_5591.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_5592_6347.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_6348_6491.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_6492_11735.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_11736_16103.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_16104_16703.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_16704_20303.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_20304_24191.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_24192_24299.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_24300_28163.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_28164_28583.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_28584_31667.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_31668_34235.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_34236_35975.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_35976_37295.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_37296_40583.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_40584_42911.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_42912_42959.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_42960_43007.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43008_43055.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43056_43103.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43104_43163.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43164_43235.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43236_43307.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43308_43355.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43356_43403.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43404_43451.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43452_43511.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43512_43559.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43560_43607.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43608_43655.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43656_43703.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_43704_47027.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_47028_47699.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_47700_48851.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_48852_48911.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_48912_49823.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_49824_54503.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_54504_54707.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_54708_54959.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_54960_55463.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_55464_55619.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_55620_55931.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_55932_57035.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_57036_57227.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_57228_57287.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_57288_58283.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_58284_58655.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_58656_58919.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_58920_59039.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_59040_60335.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_60336_60743.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_60744_61199.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_61200_65507.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_65508_65951.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_65952_67895.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_67896_68039.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_68040_70943.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_70944_70991.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_70992_71039.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_71040_71159.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_71160_71219.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_71220_72143.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_72144_73019.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_73020_74207.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_74208_74399.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_74400_74759.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_74760_76715.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_76716_76775.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_76776_76871.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_76872_77651.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_77652_79655.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_79656_81155.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_81156_81587.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_81588_83183.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_83184_84155.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_84156_85643.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_85644_86171.png The_Real_Price_of_Shared_Pointers_in_C___-_Nico_Josuttis_86172_87113.png