was raw-pointer constructor of shared_ptr a mistake?

前端 未结 3 1550
没有蜡笔的小新
没有蜡笔的小新 2021-02-09 02:57

In hindsight, given make_shared, would shared_ptr have a constructor that takes a raw pointer had it been introduced with C++11?

Are there stro

相关标签:
3条回答
  • 2021-02-09 03:01

    The problem with your logic is the belief that the reason why shared_ptr has a distinction between the managed pointer and the get pointer is because make_shared wasn't available. And therefore, if we forced everyone to use make_shared to create shared_ptr, we wouldn't need that distinction.

    This is incorrect.

    You can implement shared_ptr's pointer-based constructor without that distinction. After all, in the initial creation of a managed shared_ptr, the get pointer and the managed pointer are the same. If you wanted shared_ptr to be the sizeof(T*), you could just have the shared_ptr fetch the get pointer from the managed block. This is regardless of whether the T is embedded within the managed block.

    So the distinction really has nothing at all to do with make_shared and its ability to embed the T within the same memory as the managed block. Or rather, the lack thereof.

    No, the distinction between the managed pointer and the get pointer was created because it added features to shared_ptr. Important ones. You listed some of them, but you missed others:

    • The ability to have a shared_ptr to a base class. That is:

      shared_ptr<base> p = make_shared<derived>(...);
      

      To do that, you must have a distinction between what a particular instance points to and what the control block controls.

    • static_pointer_cast and dynamic_pointer_cast (and reinterpret_pointer_cast in C++17). These all rely on the distinction between the managed pointer and the get pointer.

      • This also includes enable_shared_from_this within base classes.
    • A shared_ptr that points to a member subobject of a type that itself is managed by a shared_ptr. Again, it requires the managed pointer to not be the same as the get pointer.

    You also seem to trivially dismiss the ability to manage pointers not created by you. That's a critical ability, because it allows you to be compatible with other codebases. Internally, you can use shared_ptr to manage things made by a library that was written in 1998.

    With your way, you divide code into two epochs: pre-C++11, and post-C++11. Your shared_ptr will do nothing for any code not explicitly written for C++11.

    And the thing about wrapping all of these features up into a single type is this:

    You don't need another one.

    shared_ptr, because it serves so many needs, can be effectively used almost anywhere. It may not be the absolutely-most-efficient-type-possible, but will do the job in virtually every case. And it isn't exactly slow in doing so.

    It handles shared ownership with polymorphism. It handles shared ownership of member objects. It handles shared ownership of memory you didn't allocate. It handles shared ownership of memory with special allocation/deallocation needs. And so forth.

    If you need shared-ownership semantics, and you need it to work, shared_ptr's got your back every time. With your suggested idea, there would always be limitations, something in your way from getting your work done.

    A type that works should be preferred by default over one that doesn't.

    0 讨论(0)
  • 2021-02-09 03:02

    In hindsight, given make_shared, would shared_ptr have a constructor that takes a raw pointer had it been introduced with C++11?

    What if you don't control the allocation of the object? What if you need to use a custom deleter? What if you need list-initialization instead of parens?

    None of these cases is handled by make_shared.

    Additionally, if you're using weak_ptr, a shared_ptr allocated via make_shared won't free any memory until all the weak_ptrs are destroyed as well. So even if you have a normal shared pointer where none of the above apply, it's possible that you may still prefer the raw pointer constructor.

    Yet another situation would be if your type provides overloads for operator new and operator delete. These may make it ill-suited for make_shared, since those overloads will not be called - and presumably they exist for a reason.

    0 讨论(0)
  • 2021-02-09 03:13

    std::shared_ptr does much more than allocate objects on the heap.

    Consider its use as an auto-closing shared file handle:

    #include <cstdio>
    #include <memory>
    
    
    int main()
    {
      auto closer = [](FILE* fp) { std::fclose(fp); };
      auto fp = std::shared_ptr<FILE>(std::fopen("foo.txt", "r"),
                                      closer);
    }
    
    0 讨论(0)
提交回复
热议问题