1 Востаннє редагувалося Betterthanyou (15.01.2017 18:20:07)

Тема: Smart pointers

Намагаюся розібратися з розумними покажчиками.

Для прикладу я створив спрощену структуру даних "Кільцевий список" (принаймні я так надіюсь, бо раніше я не робив цієї структури)

struct slist
містить два поля
data - це корисні дані
next - покажчик що вказує на наступний елемент

class list
це клас за допомогою якого можна управляти цією структурою
містить два поля
shared_ptr<slist> currentEle - поточний елемент
shared_ptr<slist> firstEle - перший елемент
і методи
first (захищений метод) - для встановлення першого елемента лиса
operator >> додати новий елемент
operator << переглянути елемент під номером n
clear - очистити список для нового заповнення

В мене виникли такі питання:
Якщо потрібно примусово звільнити пам'ять, то це робиться методом reset (чи я його не правильно застосовую) ?
Якщо я використовую розумні покажчики то мені в деструкторі нічого не потрібно прописувати ?
Навіщо weak_ptr ? Я бачив приклади, але не можу їх відтворити
Ось таку я інформацію знайшов
"розумний покажчик, який містить "слабке" посилання на об'єкт, керований покажчиком std :: shared_ptr."
...
"Якщо два об'єкти посилаються один на одного «жорстко», очевидно, вони не видаляться, якщо не вжити додаткових дій"
В мене об'єкти посилаються «жорстко» ? Вино ж замкнуті в коло (останній вказує на першого), то правильно в мене відбувається очищення ( коли буде викликана метод clear або буде здійснений вихід за області видимості ) ?

Я не застосовував weak_ptr бо не знаю де його правильно застосувати.

#include <iostream>
#include <memory>

using namespace std;

struct slist
{
    int data;
    shared_ptr<slist> next;
    ~slist(){ cout << "\nDelete - slist\n"; }
};

class list
{
public:
    list() : currentEle(nullptr) {}
    ~list() { cout << "\nDelete - list\n"; }

    list &operator >> (int d)
    {        
        if (currentEle == nullptr)
        {
            first(d);
            return *this;
        }
        unique_ptr<slist> t(new slist);
        t->data = d;
        t->next = firstEle;
        currentEle->next = move(t);
        currentEle = currentEle->next;
        return *this;
    }
    int operator << (int n)
    {
        if (firstEle == nullptr)
            return -1;

        shared_ptr<slist> t(firstEle);
        for (int i(0); i < n; i++)
            if (t->next != nullptr)
                t = t->next;
            else
                return -1;
        return t->data;
    }
    void clear()
    {
        if (currentEle == nullptr) 
            return;

        shared_ptr<slist> t;
        currentEle = firstEle;
        while (currentEle->next != nullptr)
        {
            t = move( currentEle->next );
            currentEle.reset();//delete
            currentEle = move(t);
        }
        currentEle.reset();
        firstEle.reset();
    }
private:    
    shared_ptr<slist> currentEle;
    shared_ptr<slist> firstEle;

    void first(int d)
    {
        currentEle.reset( new slist );
        currentEle->data = d;
        currentEle->next = currentEle;
        firstEle = currentEle;
    }
};

int main(int argc,char *argv[])
{
    {
        unique_ptr<list> mylist(new list);

        *mylist >> 8 >> 7 >> 9;

//демонстрація замкненості кола
        cout << (*mylist << 1000) << endl;
    }
    cout << "\nNEW\n";
    {
        unique_ptr<list> mylist(new list);

        *mylist >> 8 >> 7 >> 9 >> 10 >> 55;

        cout << endl << (*mylist << 100) << endl;
//демонстрація очищення
        mylist->clear();

        cout << endl << (*mylist << 100) << endl;
    }
    getchar();
    return 0;
}

2 Востаннє редагувалося Yola (15.01.2017 18:35:08)

Re: Smart pointers

weak_ptr застосовується коли потрібно розірвати циклічну залежність. shared_ptr досить негативно впливає на швидкодію, тому варто добре подумати чи варто його використовувати.

Гадаю для реалізації list не потрібен shared_ptr. Власне list посилається на перший елемент за допомогою unique_ptr, а той далі за допомогою такого ж unique_ptr. Тобто в list у вас залишається

unique_ptr<slist> firstEle;

Покажчик на поточний елемент, це поняття не зрозуміле, бо багато різних користувачів можуть використовувати той самий контейнер list і кожного уде свій поточний елемент, прям як індекс в масиві. Для цього існує концепція ітераторів. Ітератор зберігає в собі посилання на котейнер і на елемент.

У stl ітератори можуть переходити в недійсний стан, тобто, якщо ви видалили елемент зі списку, то ваш ітератор - недійсний, і роз'іменування його призведе до невизначеної поведінки. Це вже задача користувача думати, щоб такого не сталось.

Тоді як значеннєве навантаження розумних вказівників це власність, сирі вказівники означають, що вам просто дали цей вказівник, але видаляти вам його не треба. Отакий сирий вказівник і треба зберігати в ітераторі. Бо коли знищите ітератор, то сам елемент знищувати не потрібно.

ukrainian.stackexchange.com - це питання-відповіді з української мови
Подякували: Betterthanyou1

3 Востаннє редагувалося Betterthanyou (15.01.2017 18:44:47)

Re: Smart pointers

Yola написав:

Гадаю для реалізації list не потрібен shared_ptr. Власне list посилається на перший елемент за допомогою unique_ptr, а той далі за допомогою такого ж unique_ptr. Тобто в list у вас залишається

Я теж про це думав, але коли я доходив до додавання третього елемента я не знаю що робити

з першим зрозуміло

currentEle->next = currentEle;

тобто next вказує на currentEle

з другим теж
Я створюю новий елемент
і кажу що він вказує на перший

        unique_ptr<slist> t(new slist);
        t->data = d;
        t->next = firstEle;
        currentEle->next = move(t);

але коли я заміняю поточний елемент (currentEle) на на струпний (currentEle->next) (щоб дальше йти) я втрачаю покажчик на перший

currentEle = currentEle->next;

І ось тут я не знаю що робити.

4 Востаннє редагувалося Yola (15.01.2017 19:24:59)

Re: Smart pointers

    struct slist
    {
        int data_;
        unique_ptr<slist> next_;
        ~slist() { cout << "Delete - slist" << endl; }
    };

    auto head = make_unique<slist>();
    head->data_ = numeric_limits<int>::max();

    for (int i = 0; i < 100; ++i)
    {
        auto another = make_unique<slist>();
        another->data_ = i;
        another->next_ = move(head);
        head = move(another);
    }

    slist* it = head.get();
    while (it != nullptr) {
        cout << it->data_ << " ";
        it = it->next_.get();
    }
    cout << endl;
консоль написав:

99 98 97 96 95 94 93 92 91 90 89 88 87 86 85 84 83 82 81 80 79 78 77 76 75 74 73 72 71 70 69 68 67 66 65 64 63 62 61 60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 36 35 34 33 32 31 30 29 28 27 26 25 24 23 22 21 20 19 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1 0 2147483647
Delete - slist
Delete - slist
Delete - slist
.....

також раджу Навіщо потрібен make_unique

ukrainian.stackexchange.com - це питання-відповіді з української мови
Подякували: Betterthanyou1

5

Re: Smart pointers

В мене появилися нові питання, можливо трохи не по темі (якщо потрібно, то напишіть і я створю нову тему)

Якщо мені потрібно створити новий клас для обробки винятків, то в ньому потрібно застосовувати розумні вказівники ? Якщо так то наведіть приклад.

Я нижче навів приклад як я зазвичай пишу клас обробки винятків

Я так розумію що char *_e вказує на (const char)"Error! 124", і там ніякого динамічного виділення пам'яті не потрібно, правильно ?

class myException
{    
public:
    exception1(char *e) : _e(e) {}
    char *_e;
};
...
throw("Error! 124");

Якщо клас винятків все що робить це отримує повідомлення, то такий клас не слід писати а скористатися існуючим exception, так ?

6 Востаннє редагувалося Yola (16.01.2017 09:19:19)

Re: Smart pointers

обов'язково треба спадкуватись від std::exception

Якщо ви знаєте, що за рядок ви хочете повернути, то можна просто

class myException: public exception
{
  virtual const char* what() const throw() {
    return "Сталась помилка Х";
  }
};

Якщо рядок може бути різним, ви хочете вказати його у throw як у вашому прикладі, то

class myException : public exception
{
public:
    explicit myException(const string& msg)
        : exception(msg.c_str())
    {}

    explicit myException(const char *msg)
        : exception(msg)
    {}
};

Наявним exception користуватись не завжди добре, або майже завжди погано. Варто мати свій клас, бо так ви зможете відфільтрувати його в catch:

catch (myException& e)// Важливо отримувати через посилання, щоб не "зрізати" змінну
Прихований текст
Для нових тем гадаю краще створювати нові теми, гадаю це допоможе індексуванню в Google
ukrainian.stackexchange.com - це питання-відповіді з української мови
Подякували: Betterthanyou1