W jaki sposób zadeklarować konstruktory klasy z vector<unique_ptr>?

0

Witam,
W jaki sposób powinno się zaimplementować konstruktory (kopiujący, move) i operator= dla klasy z wektorem unique_ptr? Chodzi o utworzenie kopii unique_ptr, a nie przenoszenie "własności".Uproszczony kod klasy:

struct Node {
				std::vector<std::unique_ptr<Node>> UniqueVector;

				Node();
				Node(const Node&);
				Node(Node&&);
				Node& operator=(const Node&);
				Node& operator=(Node&&);
};
3

Chodzi o utworzenie kopii unique_ptr

Strzelam, że się przejęzyczyłeś i chciałeś napisać

Chodzi o utworzenie nowego unique_ptr, który będzie trzymał kopie tego, na co wskazuje unique_ptr, 'którego kopiujemy'.

Jeżeli tak, no to musisz dokładnie to zrobić. Skopiować to na co wskazuje unique_ptr i wrzucić to do nowego unique_ptr.
Zoba na to:

struct Node {
    std::vector<std::unique_ptr<Node>> UniqueVector;

    Node() = default;
    Node(Node&&) = default;
    Node& operator=(Node&&) = default;

    Node(const Node& other)
    {
        *this = other;
    }
    Node& operator=(const Node& other)
    {
        std::transform(other.UniqueVector.begin(), other.UniqueVector.end(),
                       std::back_inserter(UniqueVector),
                       [](const auto& node_ptr)
                       {
                           return std::make_unique<Node>(*node_ptr);
                       });

        return *this;
    }
};

W operatorze przypisania lecisz po wszystkich ptr, i robisz nowe ptr, z kopią tego na co wskazywały te, z których kopiujemy.

Tu MVCE (dla uproszczenia wypisywania, Node trzyma ptr na inty):
https://wandbox.org/permlink/3DPrfVenpZs8U6JK

1

@ranger281: Merytoryczne pytania zadawaj w odpowiedziach, nie komentarzach.
Z założenia przy move ctorze i operatorze przypisania chodzi o to, żeby ukraść to co ma przekazany obiekt, czyli będziesz chciał move'nąć cały vector, a z tym kompilator sobie poradzi sam, więc możesz to zdefaultować.
Nie możesz ich pominąć bo mając copy ctor i operator=, kompilator nie wygeneruje Ci move ctora i operatora=.

tl;dr bo chyba trochę zagmatwałem.
Jeżeli będziesz move'ował Node'y, to te dwa kwiatki muszą zostać. Mogą być zdefaultowane, kompilator sobie poradzi.

Node(Node&&) = default;
Node& operator=(Node&&) = default;
0

Czy unique_ptr na pewno jest tym czego potrzebujesz?

4

Pyta, ponieważ jeśli wywnętrz obiektu są pola typu uinque_ptr to sam obiekt powinie być unique.
Robienie klonów do rzeczy wskazywanych przez uinque_ptr jest dość podejrzane, jeśli nie ma to funkcjonalnego uzasadnienia.
Być może, w twoim wypadku użycie shared_ptr miało by większy sens i wówczas rule of zero wszystko by za ciebie załatwiła, albo po prostu ty nie potrzebujesz copy konstruktora.

1

Tak jak @MarekR22 napisał, unique_ptr jest non-copyable, więc obiekt, który jest jego właścicielem również powinien zabraniać kopiowania.
Bo jeśli potrzebujesz skopiować:

std::vector<std::unique_ptr<JakisDuzyObiekt>> obiekty

to znaczy, że tak naprawdę potrzebujesz:

std::vector<JakisDuzyObiekt> obiekty;

Koniec końców kopiujesz cały obiekt a nie tylko wskaźnik. W takim razie po co w ogóle używać wskaźników, skoro nie ma z nich pożytku?
Nie wiem jakie są Twoje intencje, ale albo klasa Node albo powinna być non-copyable, albo wskaźniki powinny być shared_ptr.

0
tajny_agent napisał(a):

Bo jeśli potrzebujesz skopiować:

std::vector<std::unique_ptr<JakisDuzyObiekt>> obiekty

Gdyby kod się kompilował bez tych konstruktorów to mogłyby być =default; nie pasuje tu też shared_ptr, bo to by była bardziej referencja, a nie identyczna wartość. Wektor ma być "kopiowany" tylko w copy-konstruktorze. Wolałbym, żeby wektor przechowywał wskaźniki, a nie obiekty, bo pozwala to w łatwy sposób "usuwać" poszczególne Node bez realokowania wszystkich innych.

Czy użycie unique_ptr< std::vector<unique_ptr<Node>> > załatwiłoby sprawę z konstruktorami? I w jaki sposób należałoby "kopiować" wartości z wektora?

2
ranger281 napisał(a):

nie pasuje tu też shared_ptr, bo to by była bardziej referencja, a nie identyczna wartość.

Przyznam, że kompletnie nie rozumiem co masz na myśli.

Wektor ma być "kopiowany" tylko w copy-konstruktorze. Wolałbym, żeby wektor przechowywał wskaźniki, a nie obiekty, bo pozwala to w łatwy sposób "usuwać" poszczególne Node bez realokowania wszystkich innych.

Usuwanie elementów z wektora może być O(1) jeśli ich kolejność nie ma znaczenia. Wystarczy zamienić ostatni element z usuwanym i zrobić pop_back()

Czy użycie unique_ptr< std::vector<unique_ptr<Node>> > załatwiłoby sprawę z konstruktorami? I w jaki sposób należałoby "kopiować" wartości z wektora?

Cały ambaras w tym, że kopiowanie unique_ptr cuchnie. Bez szerszego kontekstu nie da się nic więcej powiedzieć.

0

@tajny_agent: Jakby to wyglądało pod względem wydajności? Zakładając, że takich obiektów będzie dużo, nie powinno się ich alokować dynamicznie? W innych implementacjach (np. drzewa binarnego) Node jest zazwyczaj wskaźnikiem, pewnie z tego powodu. Nie mam pomysłu, jak to pogodzić.

2

To ostatnia rzecz którą bym się przejmował ;)
Wektor 1000 wskaźników(surowych czy opakowanych):

  • jedna alokacja miejsca w wektorze(reserve) na 1000 wskaźników
  • 1000 alokacji obiektów(new/make_unique/make_shared)

Wektor 1000 obiektów:

  • jedna alokacja miejsca w wektorze(reserve)

1001 vs 1. Oczywiście nie ma złotego środka, bo zawsze jest coś za coś..

W innych implementacjach (np. drzewa binarnego) Node jest zazwyczaj wskaźnikiem

Jeśli klasa X ma mieć listę obiektów typu X to użycie wskaźników jest konieczne. Ale zauważ, że mając relację rodzic-dziecko raczej używa się shared_ptr+weak_ptr.

1

A czemu nie optional zamiast wskaźników (skoro chodzi o możliwość łatwego ustawienia pustej wartości)? Chyba, że kluczowa jest tu wydajność, ale wtedy profiluj nie zgaduj.

0
ranger281 napisał(a):

Wolałbym, żeby wektor przechowywał wskaźniki, a nie obiekty, bo pozwala to w łatwy sposób "usuwać" poszczególne Node bez realokowania wszystkich innych.

A czy to się opłaca (w sensie wygody i czytelności, nie efektywności)? Rozumiem, że przez usuwanie rozumiesz w tej sytuacji wstawienie nullptr? Ale wtedy, przy każdym odwoływaniu się do elementów wektora musisz sprawdzać, czy nie jest usunięty...! Słabo raczej...

A może w tekim razie zamiast vector<unique_ptr<T>> wystarczy coś jak map<int, T> lub nawet set<T>? Ewentualnie unordered_... lub multi....

0
ranger281 napisał(a):

Sprawdzanie, czy !=nullptr raczej nie byłoby problemem.

Na pewno? Musiałbyś przecież robić to za każdym razem, gdy chcesz coś zrobić z jakimkolwiek elementem wektora (poza wstawieniem nowej wartości), bo a nuż jest tam pusto...

0

@tajny_agent: Mam jeszcze pytanie odnośnie osobnych unique_ptr<Node> object - można je zastąpić zwykłym obiektem w podobny sposób, co w przypadku wektora? Czy muszę pozostać przy object = std::make_unique<Node>(*other.object)?

0
ranger281 napisał(a):

@tajny_agent: Mam jeszcze pytanie odnośnie osobnych unique_ptr<Node> object - można je zastąpić zwykłym obiektem w podobny sposób, co w przypadku wektora? Czy muszę pozostać przy object = std::make_unique<Node>(*other.object)?

Pytanie nie było do mnie, ale bardzo mi się nie podoba Twoje object = std::make_unique<Node>(*other.object). Czemu nie konstruujesz obiektów od razu w make_unique tylko przekaujesz jakiś wskaźnik? Może ja czegoś nie rozumiem?

0
ranger281 napisał(a):

@tajny_agent: Mam jeszcze pytanie odnośnie osobnych unique_ptr<Node> object - można je zastąpić zwykłym obiektem w podobny sposób, co w przypadku wektora? Czy muszę pozostać przy object = std::make_unique<Node>(*other.object)?

Może tak, może nie. Szczerze, to już mi się nie chce zgadywać. Napisz konkretnie co chcesz zrobić.

1 użytkowników online, w tym zalogowanych: 0, gości: 1