Konstruktory i operatory przypisania

0

Napisałem klasę z przeładowanymi konstruktorami i operatorami przypisania, czy mogę ją jakoś ulepszyć?

  1. Czy są sytuacje kiedy nie pisze się Example& operator= tylko Example operator=?
  2. Czy zawsze trzeba pisać instrukcję if (this != &ex)?
class Example
{
private:
    size_t size;
    int *tab;
public:
    Example() = default;
    Example(size_t size) : size(size), tab(new int[size])
    {
        std::cout << "Constructor!" << std::endl;
        std::iota(tab, tab + size, 1);
    }
    ~Example()
    {
        std::cout << "Destructor!" << std::endl;
        delete[] tab;
    }
    Example(const Example& ex)
    {
        std::cout << "Copy Constructor!" << std::endl;
        size = ex.size;
        tab = new int[size];
        std::copy(ex.tab, ex.tab + ex.size, tab);
    }
    Example& operator=(const Example& ex)
    {
        std::cout << "Copy Assignment Operator!" << std::endl;
        if (this != &ex)
        {
            delete[] tab;
            size = ex.size;
            tab = new int[size];
            std::copy(ex.tab, ex.tab + ex.size, tab);
        }
        return *this;
    }
    Example(Example&& ex)
    {
        std::cout << "Move Constructor!" << std::endl;
        if (this != &ex)
        {
            size = ex.size;
            tab = new int[size];
            std::copy(ex.tab, ex.tab + ex.size, tab);
            delete[] ex.tab;
            ex.size = 0;
            ex.tab = nullptr;
        }
    }
    Example& operator=(Example&& ex)
    {
        std::cout << "Move Assignment Operator!" << std::endl;
        if (this != &ex)
        {
            size = ex.size;
            tab = new int[size];
            std::copy(ex.tab, ex.tab + ex.size, tab);
            delete[] ex.tab;
            ex.size = 0;
            ex.tab = nullptr;
        }
        return *this;
    }
};
2

Example& operator= oznacza, że próbujesz zmodyfikować bieżący obiekt.

Example operator= oznacza, ze próbujesz stworzyć nowy obiekt, a więc nowy obiekt powinien wlecieć w miejsce startego, czyli znów doszłoby do przypisania czyli to by przybrało taką rekurencyjną postać.

Ponadto mało widocznym elementem zapisu Example& operator= jest pominięcie logiki jaka może być użyta w konstruktorze.

if (this != &ex) Alternatywnei możesz napisać if (this == &ex) i wtedy zwrócić *this :D Generalnie chodzi o to, byś nie robił dodatkowo zbędnej alokacji.

czy mogę ją jakoś ulepszyć - czymkolwiek jest klasa Example to mógłbyś uniknąć całego zamieszania z int * tab stosując szablon vector.

2

Programowałem w C++ 9 lat temu :D

Kilka poprawek do tego co napisałem:

  1. masz rację Example operator= tworzy obiekt, który potem znika, ale najłatwiej to sobie uzymysłowisz gdy przekształcisz zapis:
Example a(3)
Example b(5)
Example c(2)
c = a = b;

Wtedy działania a = b stworzy tymczasowy obiekt i będzie probowało go przypisać do c. Część c = ... uruchomi ponownie operator przypisania, a tymczasowy obiekt zostanie usunięty, oczywiście to wyrażenie wygeneruje drugi tymczasowy obiekt, który już nikt nie spożytkuje.

Gdybyś zapisał Example & operator= wtedy o 2 kopie byłoby mniej.

Nie wiem jak to u Ciebie działa, ale konstruktor z słowem default rozumiem, że tam pojawi się domyślny konstruktor, który nic nie robi, a więc tab, oraz size nie bedą ustawione. Praca z takim domyślnym obiektem powinna skończyć się błedami w trakcie gdy będziesz probował zwolnić z niego pamięć.

1

@leto: Przenoszenie(konstruktor i przypisanie) masz skopane. Skopiuj wskaźnik bez żadnych alokacji.

1

@leto, zamiast robić dwie wersje operatora =, zrób tak:

    Example& operator=(Example ex)
    {
        std::swap(tab, ex.tab);
        std::swap(size, ex.size);
        return *this;
    }

I popraw konstruktor przenoszący:

Example(Example&& ex) 
: size(ex.size), tab (ex.tab)
{
    ex.tab = nullptr;
    ex.size = 0;
}
1

Na poboczu praktyki, trochę teorii https://en.wikipedia.org/wiki/Rule_ofthree(C%2B%2B_programming)

0

@tajny_agent

    Example& operator=(Example&& ex)
    {
        std::cout << "Move Assignment Operator!" << std::endl;
        if (this != &ex)
        {
            size = ex.size;
            ex.size = 0;
            tab = ex.tab;
            ex.tab = nullptr;
        }
        return *this;
    }
    Example(Example&& ex) 
        : size(ex.size), tab(ex.tab)
    {
        std::cout << "Move Constructor!" << std::endl;
        ex.size = 0;
        ex.tab = nullptr;
    }

Tak będzie prawidłowo?
Co do if (this != &ex), nie muszę tego pisać przy konstruktorach, bo one zawsze są puste?
A przy operatorach przypisania powinienem?

1

W konstruktorach nie dajesz if, bo nie ma możliwości, by ex był tym samym obiektem co this (obiekt jest w trakcie tworzenia)

0

Co do swapa, czytałem że to niezbyt w duchu C++, bo odwleka destrukcję starego obiektu wprowadzając niedeterminizm (albo coś bliskiego niedeterminizmu).

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