19

I have a double array allocated by pointer to pointer.

  // pointer to pointer
  int **x = new int *[5];   // allocation
  for (i=0; i<5; i++){
      x[i] = new int[2];
  }

  for (i=0; i<5; i++){      // assignment
      for (j=0; j<2; j++){
          x[i][j] = i+j;
      }
  }

  for (i=0; i<5; i++)   // deallocation
      delete x[i];
  delete x;

I am trying to do this using unique_ptr:

std::unique_ptr<std::unique_ptr<int>[]> a(new std::unique_ptr<int>[5]);
  for (i=0; i<5; i++)
      a[i] = new int[2];

but kept getting an error saying that no operator = matches these operands. What I am doing wrong here?

Ken Y-N
  • 14,644
  • 21
  • 71
  • 114
Evan
  • 507
  • 2
  • 7
  • 21

8 Answers8

25

You cannot assign a int* to a std::unique_ptr<int[]>, that is the cause for your error. The correct code is

      a[i] = std::unique_ptr<int[]>(new int[2]);

However, piokuc is correct, that it is highly unusual to use unique_ptr for arrays, as that's what std::vector and std::array are for, depending on if the size is known ahead of time.

//make a 5x2 dynamic jagged array, 100% resizable any time
std::vector<std::vector<int>> container1(5, std::vector<int>(2)); 
//make a 5x2 dynamic rectangular array, can resize the 5 but not the 2
std::vector<std::array<int, 2>> container1(5); 
//make a 5x2 automatic array, can't resize the 2 or 5 but is _really fast_.
std::array<std::array<int, 2>, 5> container;

All of these can be initialized and used just the same as the code you already had, except they're easier to construct, and you don't have to destroy them.

Dennis Soemers
  • 8,090
  • 2
  • 32
  • 55
Mooing Duck
  • 64,318
  • 19
  • 100
  • 158
  • 3
    Of course, `std::unique_ptr(new int[2]);` will have the wrong deleter -- that should be `std::unique_ptr(new int[2]);`. Preemptive +1 assuming you'll fix that. ;-] – ildjarn Mar 20 '12 at 23:26
  • @ildjarn: I've never used `unique_ptr` of arrays, I'm fuzzy on the syntax. Thanks! – Mooing Duck Mar 20 '12 at 23:31
  • 2
    @Evan: Be sure to upvote any answers you found helpful (with the up arrow on the left), and mark one as the "correct" answer (with the check-mark just under the arrows). – Mooing Duck Apr 17 '12 at 05:34
8

If you do not have the luxury of using a std::array or a std::vector instead of a dynamically allocated array, you can use a std::unique_ptr for a two-dimensional array in C++11 as follows:

std::unique_ptr<int*, std::function<void(int**)>> x(
    new int*[10](),
    [](int** x) {
        std::for_each(x, x + 10, std::default_delete<int[]>());
        delete[] x;
    }
);

The unique_ptr declaration takes care of allocating the row dimension of the array. The trailing () in new int*[10]() ensures that each column pointer is initialized to nullptr.

A for loop then allocates the column arrays:

for (size_t row = 0; row < 10; ++row) {
    (x.get())[row] = new int[5];
}

When the unique_ptr goes out of scope, its custom deleter lambda function takes care of deleting the column arrays before deleting the row array. The for_each expression uses the default_delete functor.

sakra
  • 62,199
  • 16
  • 168
  • 151
  • Could this be extended using `_mm_malloc` and how? – Jens Munk Oct 13 '16 at 17:02
  • Use `__mm_malloc` to allocate the memory and use `_mm_free` in the custom deleter. – sakra Oct 13 '16 at 18:09
  • Thanks, I got this working. Forgot your second piece, i.e. to allocate the individual rows (without this, I got a segmentation fault). Working on an example using variadic templates, where I can create multi-dimensional arrays, without accessing data using `.get()`. – Jens Munk Oct 13 '16 at 18:13
3
for (i=0; i<5; i++)   // deallocation
      delete x[i];
  delete x;

NO NO NO NO

delete [] x[i];
delete [] x;

// yo

amigo
  • 31
  • 1
2

The only reasons I can think of to use std::unique_ptr (or say boost::scoped_array) over std::vector for holding arrays are usually not applicable...

1) it saves 1 or 2 pointers worth of memory, depending on if you know what the size of all the arrays is [irrelevant unless you have a massive number of very small arrays]

2) in case you are just passing the array into some function that expects a C style array or raw pointer, it may feel like a more natural fit. std::vector IS guaranteed to be on sequential storage though, so passing (a.empty() ? nullptr : &a[0], a.size()) into such a function is 100% legit as well.

3) standard containers in MSVC debug mode are "checked" by default and very slow, which might be annoying when doing scientific programming on large datasets.

yonil
  • 564
  • 3
  • 12
  • 1
    An advantage of unique_ptr over vector is that you can avoid initialization, the cost of which may be significant in some cases. Ref http://stackoverflow.com/questions/96579/stl-vectors-with-uninitialized-storage and http://stackoverflow.com/questions/7546620/operator-new-initializes-memory-to-zero – goertzenator Aug 27 '13 at 14:29
2
#include <iostream>
#include <memory>

#define print(x) std::cout << x
#define println(x) std::cout << x << std::endl

int main() {
    std::unique_ptr<std::unique_ptr<int[]>[]> arr(new std::unique_ptr<int[]>[2]());
    for (int i = 0; i < 2; i++)
    {
        arr[i] = std::make_unique<int[]>(5);
        for (int j = 0; j < 5; j++) {
            arr[i][j] = j;
            println(arr[i][j]);
        }
        println(arr[i]);
    }
}
AM Z
  • 423
  • 2
  • 9
  • "#define println(x) std::cout << x << std::endl", this is what println() ! – AM Z Aug 12 '20 at 21:26
  • @Blastfurnace and for the rest, i asked the same question and they give me this solution and it worked so i thought it'll be helpful :) – AM Z Aug 12 '20 at 21:27
  • @Blastfurnace i've edited the code, is it good now? – AM Z Aug 12 '20 at 21:29
2

Your code is effectively manipulating an array of arrays of int.

In C++ you would normally want to implement it as:

std::vector<std::vector<int> > x;

This is not a good case for unique_ptr. Also, you should not need to use pointers to unique_ptr and allocate unique_ptr objects dynamically. The whole point of unique_ptr is to eliminate usage of pointers and to provide automatic allocation and deallocation of objects.

NAND
  • 663
  • 8
  • 22
piokuc
  • 25,594
  • 11
  • 72
  • 102
  • Thank you for your inputs. The way I understand unique_ptr is that it ensures that the instance it points to only has 1 reference. So using unique_ptr to point to an unique_ptr to create a matrix should be fine to use unique_ptr, given that there won't be another reference to the instance. Also, I don't understand the reason behind the last sentence. Thanks. – Evan Mar 21 '12 at 14:15
  • any RAII class should offer that same unique guarantee. Most C++ classes are RAII. So you should use the right tool for the job. vector and array should be preferred to unique_ptr here. – Mooing Duck Mar 21 '12 at 15:14
  • the reason for unique ptrs is mostly to hold dynamically allocated single objects. I can't immediately think of a reason to store an array in a unique_ptr. – Mooing Duck Mar 21 '12 at 15:16
  • There are some good use cases for it (surely!) since it is catered for specifically within the standard: the primary template `unique_ptr< T >` and specialisation `unique_ptr< T[] >`. I'm guessing it's not just for completeness since there is no such specialisation for `shared_ptr`. – boycy Jun 26 '12 at 13:05
0

An example further up inspired me for this solution

size_t k = 10;
std::unique_ptr<int*, std::function<void(int**)>> y(new int*[k](),
    [](int** x) {delete [] &(x[0][0]);
                 delete[] x;});

// Allocate the large array
y.get()[0] = new int[k*10];

// Establish row-pointers
for (size_t row = 0; row < k; ++row) {
  (y.get())[row] = &(y.get()[0][0]);
}

Here all dimensions can be dynamic and you can wrap it inside a class and expose an operator[]. Also the memory is allocated in a contiguous manner and you can easily introduce an allocator, which allocates aligned memory.

Jens Munk
  • 4,627
  • 1
  • 25
  • 40
0
for (i=0; i<5; i++)   // deallocation
      delete x[i];
delete x;

It is a common mistake here. x[i] is an array so you have to delete each array first using delete[] Then you can delete you array of int* by using delete[]

Correct desallocation would be:

for (i=0; i<5; i++)   
      delete[] x[i]; //desallocate each array of int
delete[] x; //desallocate your array of int*
x = nullptr; //good practice to be sure it wont cause any dmg