0

Say I have two functions:

const char* get_string(int id);
bool free_string(const char* str);

I want to write a std::unique_ptr wrapper for them.

From this answer I created the following:

template <auto fn>
struct deleter_from_fn {
    template <typename T>
    constexpr void operator()(T* arg) const {
        fn(arg);
    }
};

using my_string_unique_ptr =
    std::unique_ptr<const char, deleter_from_fn<free_string>>;

But then I thought, it's not a pointer to a const char, it's a pointer to an array of characters. So I replaced it with the following:

using my_string_unique_ptr =
    std::unique_ptr<const char[], deleter_from_fn<free_string>>;

Both seem to work just fine. So my questions are:

  • Which variant is preferred?
  • Is there any actual difference between the two variants?
Paul
  • 6,061
  • 6
  • 39
  • 70
  • 4
    You only *need* to differentiate between `char*` and `char[]` when you are letting `unique_ptr` do the deletion. Since you have a custom deleter that doesn't matter and is an opinion of style. – NathanOliver Apr 26 '23 at 19:17
  • 1
    `unique_ptr` holds a `new`'ed `T*` pointer and calls `delete` on it. `unique_ptr` holds a `new[]`'ed `T*` pointer and calls `delete[]` on it. `unique_ptr` and `unique_ptr` both hold a `Deleter::pointer` (if defined) or `T*` pointer and call `Deleter()` on it. – Remy Lebeau Apr 26 '23 at 20:08
  • 1
    "*it's not a pointer to a `const char`, it's a pointer to an array of characters*" - technically, it is actually a pointer to the 1st `const char` in the array, not a pointer to the array itself. A pointer to a `const char[]` array would have the type `const char (*)[N]` instead (where `N` is the size of the array). But that is not what `get_string()` is returning. So `unique_ptr` would be more appropriate. But you can use `unique_ptr` to make your type more descriptive, I suppose. – Remy Lebeau Apr 26 '23 at 20:18

1 Answers1

3

The main differences between unique_ptr<T, Deleter> and unique_ptr<T[], Deleter> are:

  • The array version has an operator[], the single version has operator* and operator->. This emphasises the fact that there isn't just a single element, since it's more difficult to get a reference to just the first element.
  • When constructing the array version from a differently typed pointer, it will check if U(*)[] is convertible to T(*)[] instead of simply U* to T*. This is not very useful for T = const char, but for class types it prevents casting derived_type[N] to base_type[N], which probably won't work right (in terms of pointer arithmetic/freeing).

Use the array version if you have an array.

Artyer
  • 31,034
  • 3
  • 47
  • 75