34

Consider:

delete new std :: string [2];
delete [] new std :: string;

Everyone knows the first is an error. If the second wasn't an error, we wouldn't need two distinct operators.

Now consider:

std :: unique_ptr <int> x (new int [2]);
std :: unique_ptr <int> y (new int);

Does x know to use delete[] as opposed to delete?


Background: this question floated through my head when I thought array type qualification of pointers would be a handy language feature.

int *[] foo = new int [2]; // OK
int *   bar = new int;     // OK
delete [] foo;             // OK
delete bar;                // OK
foo = new int;             // Compile error
bar = new int[2];          // Compile error
delete foo;                // Compile error
delete [] bar;             // Compile error
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
spraff
  • 32,570
  • 22
  • 121
  • 229
  • 1
    Instead of modifying the array type qualifier (which already exists) to work the way you show, it would be better to just eliminate all the implicit type conversions that cause all the problems with arrays. For example `new int[2]` ought to return a value of type `int (*)[2]`. Anyway, it's too late to fix all the problems with native arrays, but you can use `std::array` and avoid ever having to deal with them. `std::array` works like native arrays should work and you never have to use `new[]` or `delete[]`. – bames53 Jan 20 '12 at 19:46
  • @bames53, There is a limitation with `std::array`. You can do `new int[n]`, but you can't do `std::array`. (Unless `n` is known at compile time). But still, I avoid native arrays!# – Aaron McDaid Jan 22 '12 at 15:24
  • @AaronMcDaid Yeah, use `std::vector` if you need dynamic allocation. – bames53 Jan 23 '12 at 14:50

5 Answers5

32

Unfortunately, they don't know what delete to use therefore they use delete. That's why for each smart pointer we have a smart array counterpart.

std::shared_ptr uses delete
std::shared_array uses delete[]

So, your line

std :: unique_ptr <int> x (new int [2]);

actually causes undefined behavior.

Incidentally, if you write

std :: unique_ptr<int[]> p(new int[2]);
                     ^^

then delete[] will be used since you've explicitly requested that. However, the following line will still be UB.

std :: unique_ptr<int[]> p(new int);

The reason that they can't choose between delete and delete[] is that new int and new int[2] are exactly of the same type - int*.

Here's a related question of using correct deleters in case of smart_ptr<void> and smart_ptr<Base> when Base has no virtual destructor.

Community
  • 1
  • 1
Armen Tsirunyan
  • 130,161
  • 59
  • 324
  • 434
  • 6
    @tenfour: Because the types are exactly the same! – Armen Tsirunyan Jan 20 '12 at 11:51
  • Because they can't detect `std::shared_ptr make (int *i) {return i;}` – spraff Jan 20 '12 at 11:51
  • 6
    @tenfour: `(new int)` and `(new int[2])` both have type `int*`, there's no way to distinguish them for template specialization. – Fred Foo Jan 20 '12 at 11:51
  • "if you write `std :: unique_ptr p(new int[2]);`" - this is because the standard defines a partial specialization of `unique_ptr` for array types. So the constructor takes `int*` rather than `int (*)[]` as you'd otherwise expect, and there are some other differences (for example no `operator->`, but `operator[]` instead). – Steve Jessop Jan 20 '12 at 12:35
  • 2
    `typedef using shared_array = shared_ptr;` – spraff Jan 20 '12 at 14:06
  • 3
    @spraff: Nope, `shared_ptr` has no specialization for `T[]`. – Xeo Jan 20 '12 at 14:22
  • @Xeo: so why's that then? Something to do with the fact that `unique_ptr` has the deleter type in the template parameters, whereas `shared_ptr` uses type erasure? I suppose you write `shared_ptr p(new int[2], std::default_delete());` or thereabouts, but then you don't get `operator[]`. – Steve Jessop Jan 20 '12 at 16:56
  • @Steve: I have no idea about the rationale, but I'd believe it's something like that. – Xeo Jan 20 '12 at 17:31
  • 1
    Of course `shared_array` isn't needed because you can use `std::array` or `std::vector`, and these work just fine with smart pointers and `new` and `delete`. There's no need to use `new[]` or `delete[]` anymore. – bames53 Jan 20 '12 at 19:20
  • 3
    -1: For talking about `std::shared_array` that doesn't exist. – Nicol Bolas Jan 20 '12 at 19:20
  • 1
    @bames53: by exactly the same argument there's no need for the array specialization of `unique_ptr`, and yet somehow that slipped through into the standard. So I don't think it's a matter of course that `shared_array` didn't make it, whether it's needed or not. – Steve Jessop Jan 23 '12 at 23:34
  • @SteveJessop Yes, the same argument applies to the array specialization of `unique_ptr`. I would say not including `shared_array` actually was the natural thing (as far as I know no one thought enough of it to bother to include it in any proposal). The fact that the array specialization of `unique_ptr` was included is the oddity. – bames53 Jan 24 '12 at 00:17
  • std::array is not a reasonable replacement for shared_ptr when you don't need the size parameter since it wastes memory. – Kevin L. Stern Apr 14 '13 at 12:40
6

There is no "magical" way to detect whether a int* refers to:

  • a single heap allocated integer
  • a heap allocated array
  • an integer in a heap allocated array

The information was lost by the type system and no runtime method (portable) can fix it. It's infuriating and a serious design flaw (*) in C that C++ inherited (for the sake of compatibility, some say).

However, there are some ways of dealing with arrays in smart pointers.

First, your unique_ptr type is incorrect to deal with an array, you should be using:

std::unique_ptr<int[]> p(new int[10]);

which is meant to call delete[]. I know there is talk of implementing a specific warning in Clang to catch obvious mismatches with unique_ptr: it's a quality of implementation issue (the Standard merely says it's UB), and not all cases can be covered without WPA.

Second, a boost::shared_ptr can have a custom deleter which could if you design it to call the correct delete[] operator. However, there is a boost::shared_array especially designed for this. Once again, detection of mismatches is a quality of implementation issue. std::shared_ptr suffers the same issue (edited after ildjarn's remark).

I agree that it's not pretty. It seems so obnoxious that a design flaw (*) from the origins of C haunts us today still.

(*) some will say that C leans heavily toward avoiding overhead and this would have added an overhead. I partly disagree: malloc always know the size of the block, after all.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I think that the reason in C is a bit more complex. If dynamically allocated arrays yielded different types (as auto allocated do size being part of the type) then you could not write a `strlen` function to manage all liberals, for example, and there are no overloads in C. The alternative would be having a single array type that contained the size as a member, but that would make mapping hardware with arrays hard (were would you store the size? maintain size and a pointer to the data? that would leave us in square one: a pointer refers to either one or more elements...) – David Rodríguez - dribeas Jan 20 '12 at 13:05
  • BTW: I the list of options, don't forget that a pointer can also refer to auto or static memory, or shared memory, hardware addresses... Each of which has the two possibilities: array or single element (which can belong to an array) +1 – David Rodríguez - dribeas Jan 20 '12 at 13:08
  • My suggestion in the "background" section of the question would be a no-overhead solution, but not backwards-compatible. – spraff Jan 20 '12 at 14:04
  • @DavidRodríguez-dribeas: I think this is a false issue. `delete[]` knows how many elements were built as it invokes the destructors. The typical way of dealing with this would be using tail padding: `struct Array { size_t size; char content[]; };` and return a pointer to the content. This means that if you *assume* it is an array, then you know the size. Of course it does not help with the pointer/array distinction, but in C there is no solution. In C++, a debug implementation could "remember" if the allocation was performed by `new` or `new[]` and check that the correct `delete` is invoked. – Matthieu M. Jan 20 '12 at 14:39
  • 2
    C, having no function overloading and no templates, doesn't really *need* strict type distinctions as much as C++ does. – dan04 Jan 20 '12 at 15:32
  • @dan04: it's a point of view. Personally I think that with a proper array built-in in C, lots of memory overflows would have been avoided. Too many people forget to pass the size along the pointer to the buffer, or make mistakes when passing it; typical: `memcpy(buffer, sizeof(buffer), src)` when `buffer` is not an array but a pointer... – Matthieu M. Jan 20 '12 at 15:36
  • 1
    @MatthieuM. I agree that in C++ it is a non-issue, and also that having a proper array type in C would have helped removing many bugs, but in a systems language you need to be able to access raw areas in memory as if they were *array*s and do so without having to embed a *size* that might not fit the memory layout of the hardware. Now, that does not mean that there could not be an *array* type and a *buffer* (or *raw_array*) type. Then again, you might need to duplicate otherwise similar code to handle the two variants... I am not argueing, just bringing up potential design issues. – David Rodríguez - dribeas Jan 20 '12 at 18:04
  • @DavidRodríguez-dribeas: Yes, I understand the concern of mapping directly to raw memory. Still I think this is fairly easily address by defining a Range structure, that has either a size and pointer or just two pointers as in C++ half-open range. Then you are free to make your functions against that interface :) – Matthieu M. Jan 20 '12 at 18:56
  • 1
    @Matthieu You're making the mistake of assuming that the purpose of C is to be a high-level language that protects you from the consequences of poor programming. That is not true. C is there to make it less painful to write low-level code; some of the enormously nasty things you can do in C are there because they're _needed_ to make complex apps work at all. (This was especially true on smaller machines where the cost of overheads could mean the difference between something that would fit in memory and something that wouldn't.) – Donal Fellows Jan 21 '12 at 10:00
  • @DonalFellows: I am sorry... where did "overloads" popped up in the conversation ? While I really like them (though not to the extent that I support the awkward way they are introduced in C11), I don't think it came up here yet. – Matthieu M. Jan 21 '12 at 13:45
  • @Matthieu The real key is whether the other language (C in this case) will pass the result/argument correctly. If it's just an opaque token, it doesn't have to interpret it (and in fact, long experience tells me that it turns out to be better to put all interpretation behind an API). The problem only really comes when you're passing around something larger than a machine word, i.e. a struct or object, as that has certainly been a consistency problem in the past and it maximizes the potential for misunderstanding of object sizes (critical). – Donal Fellows Jan 22 '12 at 14:33
  • The long and short of it is that passing around an opaque-but-otherwise-ordinary pointer (which could be to anything) is easier than passing around an object by value. Yes, it has significant downsides in memory management; that's what you have to put up with. – Donal Fellows Jan 22 '12 at 14:36
  • "*`std::shared_ptr` provides the same syntax that `std::unique_ptr` does.*" If by this you mean that `std::shared_ptr p(new int[10]);` will work, it doesn't. – ildjarn Jan 24 '12 at 22:41
  • @ildjarn: right, I thought it did, though with hindsight it could be non-trivial: what if someone grabs a reference within the array ? (current with pointers). – Matthieu M. Jan 25 '12 at 07:16
3

From Microsoft's documentation:

(A partial specialization unique_ptr<Type[]> manages array objects allocated with new[], and has the default deleter default_delete<Type[]>, specialized to call delete[] _Ptr.)

I added the two final square brackets, seems like a typo as it doesn't make sense without them.

unwind
  • 391,730
  • 64
  • 469
  • 606
  • Reading Armen Tsirunyan's answer, I don't know what to make of this. I'll let it stand, as an interesting point. Perhaps I'm missing something fundamental. – unwind Jan 20 '12 at 11:57
  • I think MSDN means that if you write `shared_ptr p(new int[2])` then `delete[]` will be used. But if you write `shared_ptr p (new int[2])` this will result in UB. – Armen Tsirunyan Jan 20 '12 at 12:00
  • I suppose you could write something like `shared_ptri=shared_ptr::allocate()` and `shared_ptri=shared_ptr::allocate(n)` and prohibit construction from raw pointers. (For your own `shared_ptr`, I mean.) – spraff Jan 20 '12 at 14:15
3

std::unique_ptr is not meant for array as I quote latest boost document:

Normally, a shared_ptr cannot correctly hold a pointer to a dynamically allocated array. See shared_array for that usage.

If you want to memory management for array of pointer, you have a few options depend on your requirement:

  1. Use boost::shared_array
  2. Use std::vector of boost::shared_ptr
  3. Use boost pointer container like boost::ptr_vector
Donal Fellows
  • 133,037
  • 18
  • 149
  • 215
RoundPi
  • 5,819
  • 7
  • 49
  • 75
  • I suppose the mutual exclusion of member `operator[](size_t)` and `operator*()` would be a good clue. – spraff Jan 20 '12 at 14:17
  • You're backing up your claim about unique_ptr with a quote about shared_ptr. Weird. – sellibitze Jan 22 '12 at 14:57
  • `std::unique_ptr` is specialized for array types and will use `delete[]` if you specify one (e.g., `std::unique_ptr`). `std::vector` is a better option though. I don't see any need for `shared_array` – bames53 Jan 23 '12 at 14:49
0

Nicolai Josuttis ⟶ Note that the default deleter provided by std::shared_ptr calls delete, not delete[]. This means that the default deleter is appropiate only if a shared pointer owns a single object created with new. Unfortunately, creating a std::shared_ptr for an array is possible but wrong:

std::shared_ptr<int>(new int[10]) //error but compiles

So if you use new[] to create an array of objects you have to define your own deleter. For example

std::shared_ptr<int> ptr(new int[10], 
                         [](int* p){ delete[] p; });

or

std::shared_ptr<int> ptr(new int[10], 
                         std::default_delete<int[]>());

For me this is the best solution!

Ionut Alexandru
  • 680
  • 5
  • 17