UniqueVoidPtr.h 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. #pragma once
  2. #include <memory>
  3. #include <c10/macros/Macros.h>
  4. namespace c10 {
  5. using DeleterFnPtr = void (*)(void*);
  6. namespace detail {
  7. // Does not delete anything
  8. C10_API void deleteNothing(void*);
  9. // A detail::UniqueVoidPtr is an owning smart pointer like unique_ptr, but
  10. // with three major differences:
  11. //
  12. // 1) It is specialized to void
  13. //
  14. // 2) It is specialized for a function pointer deleter
  15. // void(void* ctx); i.e., the deleter doesn't take a
  16. // reference to the data, just to a context pointer
  17. // (erased as void*). In fact, internally, this pointer
  18. // is implemented as having an owning reference to
  19. // context, and a non-owning reference to data; this is why
  20. // you release_context(), not release() (the conventional
  21. // API for release() wouldn't give you enough information
  22. // to properly dispose of the object later.)
  23. //
  24. // 3) The deleter is guaranteed to be called when the unique
  25. // pointer is destructed and the context is non-null; this is different
  26. // from std::unique_ptr where the deleter is not called if the
  27. // data pointer is null.
  28. //
  29. // Some of the methods have slightly different types than std::unique_ptr
  30. // to reflect this.
  31. //
  32. class UniqueVoidPtr {
  33. private:
  34. // Lifetime tied to ctx_
  35. void* data_;
  36. std::unique_ptr<void, DeleterFnPtr> ctx_;
  37. public:
  38. UniqueVoidPtr() : data_(nullptr), ctx_(nullptr, &deleteNothing) {}
  39. explicit UniqueVoidPtr(void* data)
  40. : data_(data), ctx_(nullptr, &deleteNothing) {}
  41. UniqueVoidPtr(void* data, void* ctx, DeleterFnPtr ctx_deleter)
  42. : data_(data), ctx_(ctx, ctx_deleter ? ctx_deleter : &deleteNothing) {}
  43. void* operator->() const {
  44. return data_;
  45. }
  46. void clear() {
  47. ctx_ = nullptr;
  48. data_ = nullptr;
  49. }
  50. void* get() const {
  51. return data_;
  52. }
  53. void* get_context() const {
  54. return ctx_.get();
  55. }
  56. void* release_context() {
  57. return ctx_.release();
  58. }
  59. std::unique_ptr<void, DeleterFnPtr>&& move_context() {
  60. return std::move(ctx_);
  61. }
  62. C10_NODISCARD bool compare_exchange_deleter(
  63. DeleterFnPtr expected_deleter,
  64. DeleterFnPtr new_deleter) {
  65. if (get_deleter() != expected_deleter)
  66. return false;
  67. ctx_ = std::unique_ptr<void, DeleterFnPtr>(ctx_.release(), new_deleter);
  68. return true;
  69. }
  70. template <typename T>
  71. T* cast_context(DeleterFnPtr expected_deleter) const {
  72. if (get_deleter() != expected_deleter)
  73. return nullptr;
  74. return static_cast<T*>(get_context());
  75. }
  76. operator bool() const {
  77. return data_ || ctx_;
  78. }
  79. DeleterFnPtr get_deleter() const {
  80. return ctx_.get_deleter();
  81. }
  82. };
  83. // Note [How UniqueVoidPtr is implemented]
  84. // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  85. // UniqueVoidPtr solves a common problem for allocators of tensor data, which
  86. // is that the data pointer (e.g., float*) which you are interested in, is not
  87. // the same as the context pointer (e.g., DLManagedTensor) which you need
  88. // to actually deallocate the data. Under a conventional deleter design, you
  89. // have to store extra context in the deleter itself so that you can actually
  90. // delete the right thing. Implementing this with standard C++ is somewhat
  91. // error-prone: if you use a std::unique_ptr to manage tensors, the deleter will
  92. // not be called if the data pointer is nullptr, which can cause a leak if the
  93. // context pointer is non-null (and the deleter is responsible for freeing both
  94. // the data pointer and the context pointer).
  95. //
  96. // So, in our reimplementation of unique_ptr, which just store the context
  97. // directly in the unique pointer, and attach the deleter to the context
  98. // pointer itself. In simple cases, the context pointer is just the pointer
  99. // itself.
  100. inline bool operator==(const UniqueVoidPtr& sp, std::nullptr_t) noexcept {
  101. return !sp;
  102. }
  103. inline bool operator==(std::nullptr_t, const UniqueVoidPtr& sp) noexcept {
  104. return !sp;
  105. }
  106. inline bool operator!=(const UniqueVoidPtr& sp, std::nullptr_t) noexcept {
  107. return sp;
  108. }
  109. inline bool operator!=(std::nullptr_t, const UniqueVoidPtr& sp) noexcept {
  110. return sp;
  111. }
  112. } // namespace detail
  113. } // namespace c10