Why is the size of make_shared two pointers?

后端 未结 4 997
傲寒
傲寒 2021-02-05 03:23

As illustrated in the code here, the size of the object returned from make_shared is two pointers.

However, why doesn\'t make_shared work like the following

4条回答
  •  暖寄归人
    2021-02-05 04:04

    The reference count cannot be stored in a shared_ptr. shared_ptrs have to share the reference count among the various instances, therefore the shared_ptr must have a pointer to the reference count. Also, shared_ptr (the result of make_shared) does not have to store the reference count in the same allocation that the object was allocated in.

    The point of make_shared is to prevent the allocation of two blocks of memory for shared_ptrs. Normally, if you just do shared_ptr(new T()), you have to allocate memory for the reference count in addition to the allocated T. make_shared puts this all in one allocation block, using placement new and delete to create the T. So you only get one memory allocation and one deletion.

    But shared_ptr must still have the possibility of storing the reference count in a different block of memory, since using make_shared is not required. Therefore it needs two pointers.

    Really though, this shouldn't bother you. Two pointers isn't that much space, even in 64-bit land. You're still getting the important part of intrusive_ptr's functionality (namely, not allocating memory twice).


    Your question seems to be "why should make_shared return a shared_ptr instead of some other type?" There are many reasons.

    shared_ptr is intended to be a kind of default, catch-all smart pointer. You might use a unique_ptr or scoped_ptr for cases where you're doing something special. Or just for temporary memory allocations at function scope. But shared_ptr is intended to be the sort of thing you use for any serious reference counted work.

    Because of that, shared_ptr would be part of an interface. You would have functions that take shared_ptr. You would have functions that return shared_ptr. And so on.

    Enter make_shared. Under your idea, this function would return some new kind of object, a make_shared_ptr or whatever. It would have its own equivalent to weak_ptr, a make_weak_ptr. But despite the fact that these two sets of types would share the exact same interface, you could not use them together.

    Functions that take a make_shared_ptr could not take a shared_ptr. You might make make_shared_ptr convertible to a shared_ptr, but you couldn't go the other way around. You wouldn't be able to take any shared_ptr and turn it into a make_shared_ptr, because shared_ptr needs to have two pointers. It can't do its job without two pointers.

    So now you have two sets of pointers which are half-incompatible. You have one-way conversions; if you have a function that returns a shared_ptr, the user had better be using a shared_ptr instead of a make_shared_ptr.

    Doing this for the sake of a pointer's worth of space is simply not worthwhile. Creating this incompatibility, creating two sets of pointers just for 4 bytes? That simply isn't worth the trouble that is caused.

    Now, perhaps you would ask, "if you have make_shared_ptr why would you ever need shared_ptr at all?"

    Because make_shared_ptr is insufficient. make_shared is not the only way to create a shared_ptr. Maybe I'm working with some C-code. Maybe I'm using SQLite3. sqlite3_open returns a sqlite3*, which is a database connection.

    Right now, using the right destructor functor, I can store that sqlite3* in a shared_ptr. That object will be reference counted. I can use weak_ptr where necessary. I can play all the tricks I normally would with a regular C++ shared_ptr that I get from make_shared or whatever other interface. And it would work perfectly.

    But if make_shared_ptr exists, then that doesn't work. Because I can't create one of them from that. The sqlite3* has already been allocated; I can't ram it through make_shared, because make_shared constructs an object. It doesn't work with already existing ones.

    Oh sure, I could do some hack, where I bundle the sqlite3* in a C++ type who's destructor will destroy it, then use make_shared to create that type. But then using it becomes much more complicated: you have to go through another level of indirection. And you have to go through the trouble of making a type and so forth; the destructor method above at least can use a simple lambda function.

    Proliferation of smart pointer types is something to be avoided. You need an immobile one, a movable one, and a copyable shared one. And one more to break circular references from the latter. If you start to have multiple ones of those types, then you either have very special needs or you are doing something wrong.

提交回复
热议问题