41

Re: Як створити екземпляр класу і як додати в нього конструктора?

koala написав:

Так щоб delete щось, його треба спершу new. От

char *container = new char[ INITIAL_SIZE ];

можна delete-ати. Але не ваш масив.

Ну і я про те. Не подумав спочатку, як його видаляти. Але коли діло дійде до видалення, не буде ніяких проблем все це змінити.

42

Re: Як створити екземпляр класу і як додати в нього конструктора?

Чому змінюється довжина?

#include <cstdlib>
#include <iostream>
#include <cstdio>

class MyString
{
public:
    int length;
    char *str;
    MyString::MyString(char* str)
    {
        MyString::str = str;
        RecalcLength();
    }
    friend std::ostream& operator<<(std::ostream& os, const MyString& ms);

    void operator+=(char* str)
    {
        int length = MyString::length + strlen(str); 
        char *s = new char[length];
        for (int i = 0; i < MyString::length; i++)
        {
            s[i] = MyString::str[i];
        }
        for (int i = 0; i < strlen(str); i++)
        {
            s[i + MyString::length] = str[i];
        }

        std::cout << "Length before equating: " << strlen(MyString::str) << std::endl;
        MyString::str = s;
        std::cout << "Length after equating: " << strlen(MyString::str) << std::endl;
        MyString::length = length;
    }
private:
    void RecalcLength()
    {
        MyString::length = strlen(MyString::str);
    }
};

std::ostream& operator<<(std::ostream& os, const MyString& ms)
{
    os << ms.str;
    return os;
}

int main()
{
    MyString mys = "abc";
    std::cout <<"Length before += is: " << mys.length << std::endl;
    mys += "aaa";
    std::cout << "Length after += is: " << mys.length << std::endl;
    std::cout << "mys is: " << mys<< std::endl;

    system("pause >> void");
}
Прихований текст

http://не-дійсний-домен/bFxUU/f30c038224.png

p.s. круто я придумав, ага?

43

Re: Як створити екземпляр класу і як додати в нього конструктора?

1 new, 0 delete. Щось тхне протеклою пам'яттю.
Вам компілятор на це нічого не написав?
І чому ви весь час пишете MyString::? Боїтеся, що не ту змінну чи функцію викличете?

44

Re: Як створити екземпляр класу і як додати в нього конструктора?

koala написав:

1 new, 0 delete. Щось тхне протеклою пам'яттю.
Вам компілятор на це нічого не написав?
І чому ви весь час пишете MyString::? Боїтеся, що не ту змінну чи функцію викличете?

Мм, так то delete походу тре викликати перед new, бо new виділяє пам'ять в новій області якісь, ага?
Компуляктору норм.
Так, боюсь. (до того ж це так круто)

45

Re: Як створити екземпляр класу і як додати в нього конструктора?

for (int i = 0; i < strlen(str); i++)
        {
            s[i + MyString::length] = str[i];
        }
        s[length] = '\0';

я в тому ніфіга не шарю, але так ніби працює, я  так один рядочок добавив

46

Re: Як створити екземпляр класу і як додати в нього конструктора?

truesupport написав:
for (int i = 0; i < strlen(str); i++)
        {
            s[i + MyString::length] = str[i];
        }
        s[length] = '\0';

я в тому ніфіга не шарю, але так ніби працює, я  так один рядочок добавив

це термінатор?

47

Re: Як створити екземпляр класу і як додати в нього конструктора?

truesupport написав:
for (int i = 0; i < strlen(str); i++)
        {
            s[i + MyString::length] = str[i];
        }
        s[length] = '\0';

я в тому ніфіга не шарю, але так ніби працює, я  так один рядочок добавив

я так два рядочка додав

Прихований текст

http://не-дійсний-домен/bFEi7/af7710c808.png

Але не впевнений, чи так можна, адже оцим

s[length]

ми виходимо за межі масиву, ага? Мені то наср, все одно, але ж там може бути цінна інфа, як казав пан Арден.
Та й цікаво було б дізнатися, чому воно виводить 24 символа, замість потрібної кількості.

48

Re: Як створити екземпляр класу і як додати в нього конструктора?

Змінив char str[], на вказівник char* str, хотів запропонувати, але подумав що ти сам до цього дійдеш. Взагалі молодець. :)
Тепер зауваження.

1. Про ініціалізацію об’єктів. Запам’яти просто, якщо ти ініціюєш змінну без new, то вона живе до виходу за межі її видимості.

{
  {
    int aaa;
    ...
  }
  // тут змінної вже aaa і не видно і її немає, вона знищена
}

Але якщо ти використовуєш операцію new, то змінна розташовується в "кучі" (це така область пам’яті) і тобі повертається вказівник на цю змінню в кучі.

{
  {
    int* bbb = new (int);
    ...
    // тут ти может дістатися створеного об’єкта в кучі через вказівник bbb
  }
  // тут вже bbb не видно, але об’єкт в кучі залишився, бо ти його не видалив
}

якщо тобі вже не потрібен об’єкт, створенний оператором new, його потрібно видалити оператором delete, інакше пам’ять в кучі так і залишиться занятою.
Правило просте - new і delete завжди ходять в парі. Якщо об’єкт створюється за допомогою оператора new то він повинен знищуватись за допомогою оператора delete. Для знащиення масива, створеного за допомогою new, використовуються оператор delete[], не плутати!

{
  {
    int* bbb = new (int);
    ...
    // тут ти может дістатися створеного об’єкта в кучі через вказівник bbb
    delete bbb; // вказівник ще є, але об’єкт на який він вказує вже знищено
  }
  // тут вже bbb не видно, але об’єкт в кучі залишився, бо ти його не видалив
}

2. Стрічки C-типу (що являють собою массив char) по-хорошому повинні закінчуватися на символ "термінальний нуль" - '\0', бо саме цей символ вважається кінцем стрічки в багатьох операціях і в << в тому числі. Тому, коли ти з’єднуєш дві свої стрічки то розмір роби на одиницю більшим і в кінець став термінальний нуль. В тебе такий розмір дивний і виходить бо strlen шукає термінальний нуль в массиві і їй байдуже що він закнічився.

Це основні зауваження.

На бутер вже є. Навіть з ікрою. З кількома ікринками  :)

Подякували: FakiNyan1

49

Re: Як створити екземпляр класу і як додати в нього конструктора?

за вихід за межі масиву це правда, ну але можна створити ту s з довжиною не len а len + 1 і проблеми не буде. На рахунок чому 24 символи мені здається що strlen працює так.

знаходить значення за адресою s[0], якщо це не '\0', тоді воно переходить на адресу s[0] + sizeof(char) і потім дивиться що там, ну і попутно рахує скільки воно зробило таких переходів (довжину), і робить воно це до тих пір  поки не знайде отой термінатор. Але я точно не знаю, це тільки моя бурна фантазія

Подякували: FakiNyan1

50

Re: Як створити екземпляр класу і як додати в нього конструктора?

Arete написав:
Прихований текст

Змінив char str[], на вказівник char* str, хотів запропонувати, але подумав що ти сам до цього дійдеш. Взагалі молодець. :)
Тепер зауваження.

1. Про ініціалізацію об’єктів. Запам’яти просто, якщо ти ініціюєш змінну без new, то вона живе до виходу за межі її видимості.

{
  {
    int aaa;
    ...
  }
  // тут змінної вже aaa і не видно і її немає, вона знищена
}

Але якщо ти використовуєш операцію new, то змінна розташовується в "кучі" (це така область пам’яті) і тобі повертається вказівник на цю змінню в кучі.

{
  {
    int* bbb = new (int);
    ...
    // тут ти может дістатися створеного об’єкта в кучі через вказівник bbb
  }
  // тут вже bbb не видно, але об’єкт в кучі залишився, бо ти його не видалив
}

якщо тобі вже не потрібен об’єкт, створенний оператором new, його потрібно видалити оператором delete, інакше пам’ять в кучі так і залишиться занятою.
Правило просте - new і delete завжди ходять в парі. Якщо об’єкт створюється за допомогою оператора new то він повинен знищуватись за допомогою оператора delete. Для знащиення масива, створеного за допомогою new, використовуються оператор delete[], не плутати!

{
  {
    int* bbb = new (int);
    ...
    // тут ти может дістатися створеного об’єкта в кучі через вказівник bbb
    delete bbb; // вказівник ще є, але об’єкт на який він вказує вже знищено
  }
  // тут вже bbb не видно, але об’єкт в кучі залишився, бо ти його не видалив
}

2. Стрічки C-типу (що являють собою массив char) по-хорошому повинні закінчуватися на символ "термінальний нуль" - '\0', бо саме цей символ вважається кінцем стрічки в багатьох операціях і в << в тому числі. Тому, коли ти з’єднуєш дві свої стрічки то розмір роби на одиницю більшим і в кінець став термінальний нуль. В тебе такий розмір дивний і виходить бо strlen шукає термінальний нуль в массиві і їй байдуже що він закнічився.

Це основні зауваження.

На бутер вже є. Навіть з ікрою. З кількома ікринками  :)

то ви забули видалити

// тут вже bbb не видно, але об’єкт в кучі залишився, бо ти його не видалив

в третьому коді?

Подякували: Arete1

51

Re: Як створити екземпляр класу і як додати в нього конструктора?

і доречі у вас там у с++ давно уже всякі розумні вказівники є чи щось таке яка саме себе вміє знищувати, чого всі дальше * пишуть я щось не розумію

52

Re: Як створити екземпляр класу і як додати в нього конструктора?

truesupport написав:

і доречі у вас там у с++ давно уже всякі розумні вказівники є чи щось таке яка саме себе вміє знищувати, чого всі дальше * пишуть я щось не розумію

Я хз, що то є.

53

Re: Як створити екземпляр класу і як додати в нього конструктора?

FakiNyan
Дякую, якщо побачив що не видалив значить все зрозумів :)

truesupport написав:

і доречі у вас там у с++ давно уже всякі розумні вказівники є чи щось таке яка саме себе вміє знищувати, чого всі дальше * пишуть я щось не розумію

Розумні вказівники є, вони застосовують техніку RAII. Можна і їх застосувати, але я б радив спочатку довести клас до розуму без них.

Подякували: FakiNyan1

54 Востаннє редагувалося Arete (19.09.2014 23:36:22)

Re: Як створити екземпляр класу і як додати в нього конструктора?

Про RAII.
Якщо ми створюємо об’єкт за допомогою операції new, то ми повинні обов’язково знищити його за допомогою операції delete. Інакше буде "протічка" пам’яті, чи як там. Є кілька причин по яким ми можемо не знищити об’єкт:

  • 1. банальна неуважність - забули

  • 2. якийсь неопрацьований ексепшин при якому переривається робота програми

  • 3. багато виходів з метода і не перед всіма викликається delete

  • 4. інші причини, які я не можу зараз згадати.

Ось так все непросто з цим new.


А от якщо створювати об’єкт без new, то правило лише одне - він автоматично знищується коли виконання програми виходить за межі його видимості. І неважливо яким саме чином, неважливо був ексепшин чи ні - він знищується, пам’ять вивільняється і точка.

Тут виникає питання - а чи можна зробити так щоб об’єкти, які створюються операцією new поводили себе схожим чином?
Можна, якщо new-об’єкти "загорнути" в звичайні локальні об’єкти. Локальними я називаю об’єкти, які існують тільки в певній області видимості.

Логіка така:

  • 1. ми створюємо свій new-об’єкт в конструкторі звичайного об’єкта

  • 2. і знищуємо його оператором delete в дестукторі цього об’єкта.

При ініціалізації локального об’єкта викликається його конструктор і створюється наш new-об’єкт в кучі. Потім при виході за область видимості, неважливо по якій причині, локальний об’єкт знищується, а для цього викликається його деструктор. Деструктор в свою чергу знищує new-об’єкт оператором delete.

Використовуючи такий підхід нам не треба паритись про то що ми десь залишимо невивільнену пам’ять.

Приклад класу
class MyCharArray{
private:
  char* _array;
public:
  MyCharArray( int );
  ~MyCharArray();
};

MyCharArray::MyCharArray( int size ) {
  _array = new char [ size ];
  std::cout << "array created" << std::endl;
}

MyCharArray::~MyCharArray() {
  delete[] _array;
  std::cout << "array deleted" << std::endl;
}

int main( int argc, char **argv ) {
  MyCharArray a( 10 );  
  return 0;
}

output:
array created
array deleted

Ось в цій ідеї і полягає техніка RAII. Її і використовують розумні вказівники auto_ptr, shared_ptr, weak_ptr...


З.И. Якшо я десь налажав то поправте, будь-ласка. Я можу :)

Подякували: FakiNyan, truesupport2

55

Re: Як створити екземпляр класу і як додати в нього конструктора?

Пацані, якого хріна при такому коді  вилазить гівно на картинці?

#include <cstdlib>
#include <iostream>
#include <cstdio>

class MyString
{
public:
    int length;
    char *str;
    MyString::MyString(char* str)
    {
        MyString::str = str;
        RecalcLength();
    }
    MyString::~MyString()
    {
        delete[] str; //очищуємо місце, котре виділялось під оті ваші стрічки
    }

    friend std::ostream& operator<<(std::ostream& os, const MyString& ms);

    void operator+=(char* str)
    {
        int length = MyString::length + strlen(str); // отримуємо довжину нової строки
        char *s = new char[length+1]; // для термінатора
        for (int i = 0; i < MyString::length; i++)// записуємо в нову строку те, що вже було
        {
            s[i] = MyString::str[i];
        }
        for (int i = 0; i < strlen(str); i++)// дописуємо те, що треба додати
        {
            s[i + MyString::length] = str[i];
        }
                // я б ще тут викликав delete[] MyString::str; але мені ліньки
        MyString::str = s;// присвоюємо старому вказівнику на стару стрічку, вказівних на нову стрічку
        MyString::str[length] = '\0'; //- це термінатор
        MyString::length = length;// запам'ятовуємо довжину нової стрічки
    }
private:
    void RecalcLength()
    {
        MyString::length = strlen(MyString::str);
    }
};

std::ostream& operator<<(std::ostream& os, const MyString& ms)
{
    os << ms.str;
    return os;
}

int main()
{
    { MyString mys = "abc";
    for (int i = 0; i < 1000; i++)
    {
        mys += "a";
        system("cls");
        std::cout << i << std::endl;
    }
    std::cout << sizeof(mys) << std::endl;
    std::cout << mys << std::endl;
    mys.~MyString();
    }
    system("pause >> void");
}
Прихований текст

http://не-дійсний-домен/bGWs3/f9ffb43678.png

56

Re: Як створити екземпляр класу і як додати в нього конструктора?

Мені чомусь здається що деструктор силою викликати тут було поганою ідеєю

57 Востаннє редагувалося FakiNyan (20.09.2014 22:03:05)

Re: Як створити екземпляр класу і як додати в нього конструктора?

0x9111A написав:

Мені чомусь здається що деструктор силою викликати тут було поганою ідеєю

ну то я для певності
хм... якщо силою не викликати, то працює... А як мені тоді перевірити, звільняється місце на котре вказувало MyString::str чи ні?

58

Re: Як створити екземпляр класу і як додати в нього конструктора?

FakiNyan написав:
0x9111A написав:

Мені чомусь здається що деструктор силою викликати тут було поганою ідеєю

ну то я для певності
хм... якщо силою не викликати, то працює... А як мені тоді перевірити, звільняється місце на котре вказувало MyString::str чи ні?

Додай до деструктора якийсь вивід, виведе два рази.

59

Re: Як створити екземпляр класу і як додати в нього конструктора?

У вас тут багів більш, ніж тарганів на кухні чоловічого гуртожитку.

FakiNyan написав:
MyString::MyString(char* str)
    {
        MyString::str = str;
...
 MyString::~MyString()
    {
        delete[] str; //очищуємо місце, котре виділялось під оті ваші стрічки
    }

Ви берете вказівник на рядок ДЕСЬ. Де - невідомо: може, в купі, може, в стеку, може, в якійсь іще зоні. Вам це невідомо. Як цей рядок іще використовують - ви не знаєте. А потім ви його намагаєтеся delete-ати. UB як воно є: ви ж не знаєте, чи його до того створювали через new чи ні. А delete-ати можна тільки створене через new.

Ну і не викликайте декструктор напряму, він для цього не призначений. Його все одно викличуть, коли закінчиться зона видимості змінної mys (я так розумію, через це програма і падає - бо деструктор намагається знову звільнити вже звільнену пам'ять).

60

Re: Як створити екземпляр класу і як додати в нього конструктора?

koala написав:

У вас тут багів більш, ніж тарганів на кухні чоловічого гуртожитку.

FakiNyan написав:
MyString::MyString(char* str)
    {
        MyString::str = str;
...
 MyString::~MyString()
    {
        delete[] str; //очищуємо місце, котре виділялось під оті ваші стрічки
    }

Ви берете вказівник на рядок ДЕСЬ. Де - невідомо: може, в купі, може, в стеку, може, в якійсь іще зоні. Вам це невідомо. Як цей рядок іще використовують - ви не знаєте. А потім ви його намагаєтеся delete-ати. UB як воно є: ви ж не знаєте, чи його до того створювали через new чи ні. А delete-ати можна тільки створене через new.

Ну і не викликайте декструктор напряму, він для цього не призначений. Його все одно викличуть, коли закінчиться зона видимості змінної mys (я так розумію, через це програма і падає - бо деструктор намагається знову звільнити вже звільнену пам'ять).

Значить мона звільняти лише ту штуку, котра була створена в купі? (new завжди виділяє пам'ять в купі?)
Після операції +=, str вказує на масив в купі, це ж 100%, ага?
Тобто мені тепер тре в конструкторі створювати масив в купі використовуючи new, копіювати в нього масив з стеку, котрий ми отримуємо як аргумент, та присвоїти str'у вказівник на той новий масив в купі, і тоді str завжди буде вказувати на дані  створені за допомогою new, ага?