Panel użytkownika
Nazwa użytkownika:
Hasło:
Nie masz jeszcze konta?

make_unique i unique.release() a wyciek pamięci.

Ostatnio zmodyfikowano 2017-10-02 15:25
Autor Wiadomość
0x07
Temat założony przez niniejszego użytkownika
make_unique i unique.release() a wyciek pamięci.
» 2017-10-02 11:36:28
Witam zacnych Użytkowników tego forum.

Zastanawia mnie kwestia zarządzania pamięcią w przypadku użycia uniqe_ptr.release() jako wartości zwracanej przez funkcję członkowską klasy. Myślę, że najlepiej zobrazować to na przykładzie prostego kodu (kod zaczerpnięty z https://en.wikibooks.org/wiki/C%2B%2B_Programming/Code/Design_Patterns):
C/C++
class Pizza
{
public:
    void setDough( const string & dough ) {
        m_dough = dough;
    }
    void setSauce( const string & sauce ) {
        m_sauce = sauce;
    }
    void setTopping( const string & topping ) {
        m_topping = topping;
    }
    void open() const {
        cout << "Pizza with " << m_dough << " dough, " << m_sauce << " sauce and " <<
        m_topping << " topping. Mmm." << endl;
    }
   
private:
    string m_dough;
    string m_sauce;
    string m_topping;
};

class PizzaBuilder
{
public:
    virtual ~PizzaBuilder() { }
   
    Pizza * GetPizza() {
        return m_pizza.release();
    }
    void CreateNewPizzaProduct() {
        m_pizza = make_unique < Pizza >();
    }
    virtual void buildDough() = 0;
    virtual void buildSauce() = 0;
    virtual void buildTopping() = 0;
protected:
    unique_ptr < Pizza > m_pizza;
};

class Cook
{
public:
    void openPizza() {
        m_pizzaBuilder->GetPizza()->open();
    }
    void MakePizza( PizzaBuilder * pb ) {
        m_pizzaBuilder = pb;
        m_pizzaBuilder->CreateNewPizzaProduct();
        m_pizzaBuilder->buildDough();
        m_pizzaBuilder->buildSauce();
        m_pizzaBuilder->buildTopping();
    }
private:
    PizzaBuilder * m_pizzaBuilder;
};

Klasa Cook jako argument metody MakePizza przyjmuje adres obiektu abstrakcyjnej klasy bazowej lub pochodnych. Następnie wywołuje metodę CreateNewPizzaProduct, która inicjalizuje wskaźnik uniqe_ptr domyślnie zainicjalizowanym obiektem Pizza po czym inicjalizuje jego składowe prywatne przy pomocy metod klas pochodnych klasy PizzaBuilder. Wywołanie metody openPizza klasy Cook prowadzi do wywołania metody GetPizza klasy PizzaBuilider. W metodzie tej wskaźnik unique_ptr jest pozbawiany własności przy pomocy release(), zaś sam obiekt Pizza jest przypisywany do tymczasowego surowego wskaźnika Pizza*, który służy do wywołania funkcji open klasy Pizza. Moje wątpliwości dotyczą zagadnienia wycieku pamięci, ponieważ z tego co mi wiadomo obiekt "odebrany" wskaźnikowi unique_ptr należy przypisać do innego wskaźnika, aby później móc go zniszczyć. Tutaj jednak po zakończeniu działania metody openPizza, wskaźnik Pizza* posiadający obiekt Pizza jest automatycznie zwalniany. Czy w ten sposób odzyskiwana jest pamięć po obiekcie Pizza? Przyznam szczerze, że inteligentne wskaźniki są dla mnie tematem nowym i nie w pełni oczywistym. Czy jest ktoś tak uprzejmy, aby wytłumaczyć mi, w którym miejscu dochodzi (lub nie dochodzi) do zwolnienia pamięci? A może moje rozumowanie jest błędne w zupełnie innym miejscu; jeśli tak, to w którym.

Dziękuję każdemu cierpliwemu Użytkownikowi za okazaną pomoc.

Pozdrawiam serdecznie
0x07

P-165397
Monika90
» 2017-10-02 13:19:05
Kod jest wbrew zasadom i nie zwalnia pamięci. Funkcja GetPizza powinna zwracać unique_ptr i już będzie lepiej.
P-165400
0x07
Temat założony przez niniejszego użytkownika
» 2017-10-02 14:03:42
Powinienem do tego użyć std::move czy raczej utworzyć lokalny unique_ptr i zainicjalizować go przy pomocy release()? Czy fragment kodu poniżej jest już prawidłowy? Czy pomiędzy obiema metodami istnieje jakaś wyraźna różnica (np. dla wydajności przy częstszym stosowaniu obydwu składni), czy to po prostu kwestia gustu? Można to napisać jeszcze inaczej?
C/C++
std::unique_ptr < Pizza > GetPizza() {
    return std::move( m_pizza );
}

// ALBO

unique_ptr < Pizza > GetPizza() {
    unique_ptr < Pizza > pz( m_pizza.release() );
    return pz;
}

W ten sposób wskaźnik m_pizza zostanie usunięty automatycznie wraz z końcem życia obiektu PizzaBuilder, a przekazany obiekt zostanie usunięty wraz z zakończeniem życia zwracanego przez funkcję unique_ptr. Dobrze rozumiem?

PS
Wiem, że zadaję bardzo dużo pytań, ale usprawiedliwiam to niepohamowanym apetytem na wiedzę z zakresu C++ (wiem, wiem, muszę uważać żeby się nie "przejeść"). Z kolei w sieci bardzo trudno jest natrafić na zadowalającą wypowiedź na temat bardzo skonkretyzowanego pytania.

@Edit:
Chyba już wszystko rozumiem. Dzięki @Monika90 za dodatkową odpowiedź poniżej. Temat wyczerpany, zamykam.
P-165401
Monika90
» 2017-10-02 15:25:23
Obydwa sposoby robią to samo, ale ten pierwszy jest lepszy bo kod jest krótszy i wprost wyraża intencje programisty. Ten drugi wygląda jak napisany przez kogoś, kto nie wiedział że istnieje std::move.
P-165404
« 1 »
  Strona 1 z 1