Zadanie

Skopiuj zawartość tablicy wskazywanej przez std::unique_ptr do tablicy wskazywanej przez drugi unique_ptr:

#include <iostream>
#include <memory>

int main()
{
    constexpr size_t SIZE = 10;
    
    std::unique_ptr<int[]> uptr1{ new int[SIZE]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} };
    
    auto uptr2 = std::make_unique<int[]>(SIZE);
    
    // Implement your solution here
    
    for (size_t i = 0; i < SIZE; ++i)
    {
        std::cout << uptr2[i] << ' ';
    }
}

Uruchom w edytorze

0 0 0 0 0 0 0 0 0 0

Rozwiązanie

Inteligentny wskaźnik (ang. smart pointer) typu std::unique_ptr (plik nagłówkowy memory) posiada specjalizację swojego szablonu dla typu tablicowego. Oznacza to, że nasza tablica (utworzoną z wykorzystaniem tablicowego operatora new []) reprezentowana przez zwykły wskaźnik może być zarządzania przez inteligentny wskaźnik w postaci typu std::unique_ptr:

std::unique_ptr<int[]> uptr1{ new int[SIZE]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} };

Dzięki takiemu podejściu otrzymujemy automatyczne zarządzenie pamięcią zgodne z idiomem RAII (ang. Resource Acquisition Is Initialization). W momencie wyjścia ze swojego zakresu (ang. scope), std::unique_ptr wywoła (w ramach swojego destruktora) więc odpowiedni operator tablicowy delete []. Taką tablicę możemy również stworzyć wykorzystując funkcję  std::make_unique (plik nagłówkowy memory):

auto uptr2 = std::make_unique<int[]>(SIZE);

Po wywołaniu tej funkcji elementy tablicy zostaną zainicjalizowane domyślnymi wartościami – w naszym przypadku będą to same zera ponieważ typem przechowywanym w tablicy jest int.

Najważniejsza z punktu widzenia tego zadania jest obecność operatora [] (zwracającego referencję do elementu przechowywanego pod danym indeksemtablicy) dla typu std::unique_ptr w przypadku jego specjalizacji tablicowej. Dzięki temu do elementów tablicy możemy odwoływać się standardowo za pomocą indeksów wewnątrz pary nawiasów ”[]”. Jednocześnie należy pamiętać, że specjalizacja tablicowa dla typu std::unique_ptr pozbawiona jest operatora * oraz operatora ->, dzięki którym możemy korzystać z inteligentnych wskaźników (dla pojedynczych obiektów) tak jakby były to standardowe ”surowe” wskaźniki.

Dzięki operatorowi [] dla typu std::unique_ptr, możemy naszą tablicę użyć wraz z algorytmamibiblioteki standardowej języka C++. Nasze rozwiązanie oprzemy na algorytmie std::copy (plik nagłówkowy algorithm). W pierwszej chwili możemy napisać coś takiego:

std::copy(&uptr1[0], &uptr1[SIZE], &uptr2[0]);

Powyższy zapis jest zgodny z tym jak użylibyśmy standardowej ”gołej” tablicy. Inaczej mówiąc, wskaźnik do pierwszego elementu odpowiada iteratorowi początkowemu, natomiast wskaźnik wskazujący za ostatnim elementem (indeks jest równy liczbie elementów tablicy) odpowiada iteratorowi końcowemu dla całej tablicy. Jednakże w przypadku operatora [] dla typu std::unique_ptr zdefiniowane jest jedynie użycie indeksu mniejszego niż liczba elementów tablicy. W przeciwnym wypadku należy liczyć się z niezdefiniowanym zachowaniem (ang. undefined behaviour). Rozwiązanie tego problemu jest proste i polega na dodaniu liczby elementów tablicy do wskaźnika do pierwszego elementu tablicy zwróconego przez operator []:

std::copy(&uptr1[0], &uptr1[0] + SIZE, &uptr2[0]);

Oto gotowe rozwiązanie:

#include <iostream>
#include <memory>
#include <algorithm>

int main()
{
    constexpr size_t SIZE = 10;
    
    std::unique_ptr<int[]> uptr1{ new int[SIZE]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9} };
    
    auto uptr2 = std::make_unique<int[]>(SIZE);
    
    // Implement your solution here
    std::copy(&uptr1[0], &uptr1[0] + SIZE, &uptr2[0]);
    
    for (size_t i = 0; i < SIZE; ++i)
    {
        std::cout << uptr2[i] << ' ';
    }
}

Uruchom w edytorze

0 1 2 3 4 5 6 7 8 9