Konstruktor przenoszący - problem ze zrozumieniem odwołania się dwóch obiektów do tego samego adresu

0

Mamy klasę Useless

class Useless
{
private:
    int n;
    char * pc;
   ...
}

Zdefiniowany jest konstruktor przenoszący:

Useless::Useless(Useless && f)
{
    n = f.n;
    pc = f.pc;
    f.pc = nullptr;
    f.n = 0;
}

Jest tez przeciążony operator+():

Useless Useless::operator+(const Useless & f) const
{
    Useless temp = Useless(n + f.n);
    ...
    return temp;
}
    

Następuje wywołanie konstruktora przenoszącego:

Useless one(10, 'x');
Useless three(20, 'o');
Useless four (one + three); // w tym miejscu

Nie potrafię zrozumieć co dzieje się z pamięcią obiektu tymczasowego (r-wartością) skoro w definicji konstruktora przenoszącego odwołanie f.n = 0 wydaje mi się powinno wpłynąć na to co jest pod tym samym adresem n = f.n a okazuje się, że tak nie jest.

0

Nie wiem czy dobrze rozumiem, bo może powinienem iść już spać, ale w jaki sposób f.n = 0 miałoby wpłynąć na n, poprzez n=f.n skoro ta operacja jest wykonywana później?

0

wydaje mi się powinno wpłynąć na to co jest pod tym samym adresem n = f.n a okazuje się, że tak nie jest.
n musiało by być wskaźnikiem lub referencją żeby operować na tym samym adresie pamięci, a jest zwykłym intem, więc nie wskazuje na to samo, tylko sobie kopiuje do innego miejsca w pamięci.

0
nullpt4 napisał(a):.`

n musiało by być wskaźnikiem lub referencją żeby operować na tym samym adresie pamięci

To moje pierwsze zetknięcie z semantyką przeniesienia. Faktycznie już nieco późno było kiedy to pisałem... Teraz rozumiem. Ale nie wiem jeszcze jak działa mechanizm referencji do r-wartości.
Adres r-wartości (obiektu tymczasowego) zarezerwowany jest na stosie? I tylko dane składowe obiektu tworzone na stercie mają szanse istnieć po usunięciu obiektu tymczasowego?
A gdybym chciał stworzyć refernecję do r-wartości, która powiedzmy wynosi 5:

int && r = 5;

Gdzie przechowywana jest wartość liczby 5?
Bo rozumiem, że operujemy na tych samych adresach.

0

Bo rozumiem, że operujemy na tych samych adresach. których adresach?
Z tego co rozumiem to przy zapisie int && r = 5; jest tworzony obiekt tymczasowy który ma wartość 5,
i nie widze powodu dlaczego miałby być alokowany na stercie.

EDIT:
tzn. zmienne mają adresy, wartości ich NIE mają.

0

@nullpt4:

nullpt4 napisał(a):

Bo rozumiem, że operujemy na tych samych adresach. których adresach?

A czy nie jest tak, że wyrażenie int && r = 5; przypisuje referencji r adres literału 5 (a może dokładniej zmiennej tymczasowej, która ma wartość 5)?
I skoro obiekt tymczasowy jest niszczony, to na co wskazuje r? Gdyby to było miejsce na stosie, taka referencja nie miała by sensu, dobrze myślę?

1

Definicja r jako

int && r = 5;

czy też jako

int r = 5;

kompiluje się do identycznego kodu https://godbolt.org/z/re69o9

1

A czy nie jest tak, że wyrażenie int && r = 5; przypisuje referencji r adres [ ...] zmiennej tymczasowej, która ma wartość 5
dokładnie tak jest

I skoro obiekt tymczasowy jest niszczony, to na co wskazuje r?
jest niszczony jak wyjdziemy ze scope'a zmiennej r

czyli

{
   int &&r=5;
   ....
}
// tutaj juz nie ma sensu odwoływać się do zmiennej r, a przynajmniej kompilator będzie narzekał jeśli ktoś by chciał tak zrobić.

Gdyby to było miejsce na stosie, taka referencja nie miała by sensu, dobrze myślę?
Dobrze myślisz, ale nawet jeśli nie ma sensu, to nie znaczy że nie da się jej używać dla zmiennych które są tworzone tylko na stosie :P

rvalue przydaje się gdy mamy doczynienia z obiektami które alokują pamięć dynamicznie (na stercie) np std::vector.
i int &&r=5 raczej nie ma sensu, to std::vector<int> v&& jak najbardziej ma.

Cały myk polega na tym, że przy move semantics to co jest na stosie jest kopiowane, a to co na stercie "przepinane" (unikamy kopiowania sterty, poprzez proste przepięcie wskaźnika).

i to by wyglądało jakoś tak:

struct Foo {
    int a = 5;
    int* ptr = new int[2048];
 
   Foo(Foo&& f) {
        a=f.a; //kopia

        ptr=f.ptr // przypisanie wskaźnika            |\ tutaj następuje
        f.ptr = nullptr; // wyzerowanie               |/  przepięcie wskaźnika

        f.a = 0;  // wyzerowanie
    }
};

EDIT
W sumie teraz zauważyłem, że sam już podałeś podobną implementację,
i teraz to już nie wiem czy odpowiedziałem na Twoje wątpliwości :D

0

@nullpt4: Jest to dla mnie nowość więc muszę trochę więcej czasu poświęcić, żeby to przyswoić, ale tak, dziękuję, na tym etapie moje wątpliwości zostały rozwiane :)

TomaszLiMoon napisał(a):

kompiluje się do identycznego kodu https://godbolt.org/z/re69o9

Fajna stronka, nie znałem jej wcześniej. Dzięki za pomoc :)

2

Cały myk polega na tym, że przy move semantics to co jest na stosie jest kopiowane

Nie jestem do końca pewien, ale move semantics można zastosować także do obiektów utworzonych na stosie (czyli bez kopiowania). Jeżeli się mylę to niech ktoś mnie poprawi.
Można użyć do tego do tego pmr::vector i zastosować lazy initialization przy użyciu optional (C++17):

#include <iostream>
#include <array>
#include <vector>
#include <optional>
#include <cstddef>
#include <memory_resource>

using namespace std;

struct Foo
{
    optional<array<byte,2000>> buf {nullopt};
    optional<pmr::monotonic_buffer_resource> pool {nullopt};
    optional<pmr::vector<int>> coll {nullopt};

    Foo()
    {
        buf = array<byte,2000>{};
        pool.emplace( buf->data(), buf->size());

        coll.emplace(&(*pool));

        for( int i {0} ; i<100 ; ++i )
        {
           coll->emplace_back(i+i);
        }
    }

    Foo( Foo&& foo )
    {
        coll = move(foo.coll);
    }
};

int main()
{
    Foo foo;
    cout <<  &foo.coll.value().at(3) << endl;
    Foo foo_move = move(foo);
    cout <<  &foo_move.coll.value().at(3) << endl;
}

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