1 Востаннє редагувалося Олександр Ковальчук (29.12.2022 00:59:31)

Тема: Порушення інкапсуляції

Доброго дня.
Питання.
Наступний код є порушенням інкапсуляції?

class Myclass : public baseAbstract
{
    //---------
    //---------
}
class MyClassArr
{
    private:
        int size;
        baseAbstract** arr = nullptr;
    public:
        MyClassArr(int size):size(size)
        {
            arr = new baseAbstract*[size]{};
            //--------------------
            //--------------------
        }
        ~MyClassArr()
        {
            for(int i = 0;i < size;i++)
                delete arr[i];
            delete[] arr;
        }
        baseAbstract* getMyClass(int n)  //???
        {
            return arr[n];
        }
}

Діло в тому що в подальшому можна написати слідуюче:

MyClassArr marr(6);
baseAbstract * mclass = arr.getMyClass(3);
delete mclass;

В останньому рядку було видалено елемент масиву.... а клас MyClassArr про це навіть не "здогадується"....

2 Востаннє редагувалося koala (28.12.2022 11:16:55)

Re: Порушення інкапсуляції

Так, але не тому, що вийшов висячий вказівник, це окрема проблема в будь-якій мові програмування з явними вказівниками (крім, хіба що, Rust, та й там за бажання можна отримати), а тому, що доступ до даних класу надано не через метод, а напряму, через вказівник. Тепер зовнішній код може змінити стан об'єкта, а об'єкт буде не в курсі. Додамо в клас поле, що містить суму усіх елементів масиву (якщо їх можна додавати). Якби методи доступу виглядали десь так

const baseAbstract& get_value(int n) {
  return *arr[n];
}
void set_value(int n, int m, baseAbstract *value) {
  total_sum -= *arr[n];
  arr[n][m] = value;
  total_sum += *value;
}

то всі зміни були б інкапсульовані. А тут є порушення інкапсуляції - поле total_sum може не відповідати поточній сумі.

3 Востаннє редагувалося Олександр Ковальчук (28.12.2022 16:05:46)

Re: Порушення інкапсуляції

koala написав:
const baseAbstract& get_value(int n) {
  return *arr[n];
}

Саме так я й реалізував...хіба що без const оскільки це не дозволить викликати не константні методи baseAbstract та похідного Myclass. Хоча це не дуже зручно.
Оскільки абстрактний клас не може мати екземпляра то змінну яка приймає значення можна реалізувати тільки так :

  baseAbstract& result = marr.get_value(0);

A оскільки змінна result мусить бути ініціалізована при об'явленні i її не переініцілізувати  то слідуючий код не спрацює:

 baseAbstract& result = marr.get_value(0);
for(int i = 1; i < size; i++)
{
      result = marr.get_value(i);
      result.showInfo();
      result.doWork();
      //............
      //...............
}


Прийшлось так:

void resultProc(baseAbstract& result)
{
      result.showInfo();
      result.doWork();
      //............
      //...............
}

for(int i = 1; i < size; i++)
{
     resultProc(marr.get_value(i))
}

4

Re: Порушення інкапсуляції

Величезна проблема мови C++ полягає в тому, що вона не нав'язує програмісту "правильне" рішення, а лишає йому самостійно обирати засоби. В результаті дуже легко потрапити в якусь подібну пастку, яку дуже легко уникнути при правильному виборі класової структури.
Наслідування - це відношення "це такий". Тобто MyClassArr - це такий baseAbstract, який ще містить масив посилань на baseAbstract, і ще кілька методів для роботи з ними. Це, в цілому, досить дивна ситуація (хоча й не неможлива). Але без знання предметної області і завдання я не можу підказати, як правильно це все треба побудувати.

5 Востаннє редагувалося Олександр Ковальчук (28.12.2022 20:42:44)

Re: Порушення інкапсуляції

Є абстрактний клас CombatVehicle :

class CombatVehicle
{
private:
    std::string  type;
    std::string  model;
protected:
    struct minMax
    {
        double min;
        double max;
    };
    double  health;
    double  last_damage;
    double  last_defence;

public:
    CombatVehicle();
    CombatVehicle(std::string type, std::string model, double health);
    virtual ~CombatVehicle(); 
    bool IsDestroyed() const{ return health <= 0; };
    void ShowShortInfo() const { std::cout << "  " << type << " :  " << model << std::endl; };
    virtual void ShowInfo(int X, int Y, HANDLE handle);
    virtual double Attack() const = 0;
    virtual void   Defense(double damage) = 0;
    std::string getType()const { return type;}
};

Від цього класу наслідуються нащадки Tank , ArmoredCar , AirDefenseVehicle....

lass Tank : public CombatVehicle
{
private:
    double recharge_time;
    double shot_acuracy;
    double armor_thickness;
    static constexpr  minMax rtime     { 1.5 , 2 };
    static constexpr  minMax acuracy   { 2,3 };
    static constexpr  minMax thickness { 30,50 };
    static constexpr  minMax thealth   { 1500,2000 };
    static constexpr const char*  models[5] { "M1A1 Abrams","Leopard","T-64BM Oplot","Merkava","Leclerc" };
public:
    Tank();
    Tank(std::string type, std::string model, double health, double recharge_time,double shot_acuracy,double armor_thickness);
    void   ShowInfo(int X, int Y,HANDLE handle) ;
    double Attack() const { return (100 * shot_acuracy) / recharge_time; }
    void   Defense(double damage); 
};

Існує клас Troops який містить армію техніки в масиві який заповнюється випадковим чином:

class Troops
{
private:
    enum class VehicleType
    {
        Tank = 1,
        ArmoredCar,
        AirDefenseVehicle
    };

    static const size_t max_count              = 10;
    static const size_t min_count              = 5;
    
    std::string name;
    int vehicles_count;
    CombatVehicle** troops;
public:
    Troops();
    ~Troops();
    Troops(std::string name,int vehicles_count);
    bool isVechicleExist() { return vehicles_count > 0; }
    void show(int X, int Y, HANDLE handle) const;
    CombatVehicle& getVehicle() { return *troops[vehicles_count - 1]; };
    void dellCurentVehicle();
    void v_show(int index,int prnt_index,int X, int Y, HANDLE handle) const;
};

Також існує клас Battle який створює дві армії (Troops які в свою чергу генерують випадковим чином екземпляри класів похідних від CombatVehicle  тобто Tank і тд. і зберігають в масиві) і проводить битву двох армій беручи з кожної армії по одному екземпляру техніки ... і так до того моменту коли в одній з армій не закінчиться техніка.

class Battle
{
private:
    Troops troops1;
    Troops troops2;
    int pause = 500;
    bool rаund(CombatVehicle& veh1, CombatVehicle& veh2, HANDLE handle);
    bool isDestroyed(CombatVehicle& veh, HANDLE handle , bool swich);
    HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE);
public:
    Battle(std::string tr1name, std::string tr2name,int trvCount, int tr2vCount = 0);
    void battleStart();
};

Саме  про методи CombatVehicle& getVehicle() та  bool rаund(CombatVehicle& veh1, CombatVehicle& veh2, HANDLE handle) було питання....
Все працює ... але виклик  rаund(getVehicle(),getVehicle(),handle) ... якось...

6

Re: Порушення інкапсуляції

Ну так а де ота структура з масивом і одночасним успадкуванням? Загалом, звісно, було б правильніше, щоб raund (це round?) не приймав посилання на конкретні бойові одиниці, а, наприклад, сам їх генерував із troops1 і troops2, він же має до них доступ. Але можна і так. Якщо хочете суворо без порушення інкапсуляції - зробіть в Troops і CombatVehicle методи

CombatVehicle::fight(CombatVehicle& other) {
  //логіка бою двох одиниць
}
Troops::fight(Troops& other) {
  getVehicle()->fight(oher->getVehicle());
}

а метод getVehicle зробіть приватним.
Загальна порада: замість масивів використовуйте вектори, а замість звичайних вказівників - розумні вказівники, скажімо, std::shared_ptr чи std::unique_ptr, по ситуації. Тут більше тягне на unique_ptr, у вас же чітка структура володіння (Battle володіє Troops, Troops володіють CombatVehicle). Коли не зрозуміло, в який момент завершується існування об'єкта, потрібен std::shared_ptr (той самий випадок із висячим вказівником).
А от HANDLE тут явно надлишковий. За введення-виведення має відповідати окрема підсистема (наприклад, функція), а не ігрова логіка; ну і тим більше вона не має бути прив'язана до конкретної ОС. Хай ігрові класи лише генерують інформацію для виведення (можна, скажімо, стрічки).

7 Востаннє редагувалося Олександр Ковальчук (28.12.2022 22:46:26)

Re: Порушення інкапсуляції

koala написав:

(це round?)

Так...поправив... *PARDON*

koala написав:

Якщо хочете суворо без порушення інкапсуляції - зробіть в Troops і CombatVehicle методи

Було б добре...але методи прописані в завданні:

• IsDestroyed () – перевіряє чи машина знищена (true/false) відповідно до рівня health
• ShowInfo () – відображає всю актуальну інформацію
• Attack () = 0 – виконує атаку (повертається кількість нанесеного урону)
• Defense (damage) = 0 – виконує оборону від атаки, приймаючи нанесений урон
Для проведення раунда створити окрему функцію Round (bm1, bm2)
koala написав:

Загальна порада: замість масивів використовуйте вектори, а замість звичайних вказівників - розумні вказівники, скажімо, std::shared_ptr чи std::unique_ptr, по ситуації

Згідний...але поки ще рано...

koala написав:

А от HANDLE тут явно надлишковий.

Я розумію...але не хочеться писати окрему систему....як для домашки і так згодиться... :)

8

Re: Порушення інкапсуляції

Олександр Ковальчук написав:

Для проведення раунда створити окрему функцію Round (bm1, bm2)

А сказано, що ця функція має бути саме в Battle? Якщо ні, то може бути friend в CombatVehicle.

І на майбутнє: одразу викладайте завдання, бо інакше це досить нецікава гра у "відгадайте, як мені треба, а я буду казати, як неправильно".

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

9 Востаннє редагувалося lucas-kane (29.12.2022 00:11:47)

Re: Порушення інкапсуляції

Олександр Ковальчук написав:

Доброго дня.
Діло в тому що в подальшому можна написати слідуюче:

MyClassArr marr(6);
baseAbstract * mclass = _arr.getMyClass(3);
delete mclass;

В останньому рядку було видалено елемент масиву.... а клас MyClassArr про це навіть не "здогадується"....

Це не так. Він не є елементом масиву.

По-перше, baseAbstract * mclass - буде створено у СТЕКУ.
По-друге,  baseAbstract * mclass = marr.getMyClass(3); присвоїть цій ділянці пам'яті адресу із КУПИ. В даному випадку 4-го  (4-1=3) елементу масиву вказівників - marr. Аж ніяк не буде навпаки.

class Myclass : public baseAbstract {}
class MyClassArr
{
    public:
        MyClassArr(int size):size(size)
        {
            arr = new MyClass*[size]{}; // тут потрібно виділяти пам'ять не під похідні елементи класу, а базові (baseAbstract)
        }

Ви не вірно звільняєте пам'ять.
Створюєте масив вказівників "Базового класу"

arr = new MyClass*[size]{};

А Видаляєте елементи, котрих ще навіть і не існує.

for(int i = 0;i < size;i++) delete arr[i];

Нагадаю. Масиви видаляються

delete [] arr

, під-масиви або елементи масиву -

delete [] arr[i];
delete arr[i]

.
Зразок коду, як потрібно працювати із розподілом пам'яті для Вашого завдання.

#include <iostream>
using namespace std;

class Base { /* ... */ };
class Derived_1 : public Base { /* ... */ };
class Derived_2 : public Base { /* ... */ };
class Derived_3 : public Base { /* ... */ };
class Array
{
public:
    ~Array()
    {
        /* ... */
        for (size_t i = 0; i < s; ++i)
            if (p[i])
                delete[] p[i], p[i] = nullptr;
        /* ... */
        delete[] p;
    }
    Array(size_t siz) : s(siz) {
        p = new Base *[s];
    }
    void setBase(Base *pb, size_t id) { p[id] = pb; }
    Base *getBase(size_t id) { return p[id]; }

private:
    Base **p = nullptr;
    size_t s;
};
int main()
{
    Array a1(2);
    Base *b1 = new Derived_1[4]{};
    Base *b2 = new Derived_3[7]{};
    a1.setBase(b2, 0);
    a1.setBase(b1, 1);
    
    // Видалення b1, b2 виконає ~Array    
    
    Array a2(3);
    a2.setBase(new Derived_1[5]{}, 0);
    a2.setBase(new Derived_3[3]{}, 2);
    a2.setBase(new Derived_2[1]{}, 1);

    // Видалення об'єктів Base виконується вручну
    delete[] a2.getBase(2), a2.setBase(nullptr, 2);
    delete[] a2.getBase(0), a2.setBase(nullptr, 0);
    delete[] a2.getBase(1), a2.setBase(nullptr, 1);

    return 0;
}

10 Востаннє редагувалося Олександр Ковальчук (29.12.2022 01:06:07)

Re: Порушення інкапсуляції

lucas-kane написав:

Це не так....

Вибачте ... але це був просто приклад коду і до того ж з помилкою.
Там має бути масив абстрактного класу. І так не дописав видалення масиву. Питання було не в тому.
Ініціалізацію масиву не писав...замінив рисочками....код для прикладу.
Вже виправив.

lucas-kane написав:
for (size_t i = 0; i < s; ++i)
            if (p[i])
                delete[] p[i], p[i] = nullptr;
        /* ... */
        delete[] p;.

А ось тут не зрозумів....
Для чого delete[ ] p[ i ] ?  там вказівник на об'єкт а не на масив.
Якби це був двувимірний масив тоді видаляти саме так.

for (size_t i = 0; i < s; ++i)
            if (p [i ])
                delete p[ i ], p[ i ] = nullptr;
        /* ... */
        delete[ ] p;.

Так має бути...

11 Востаннє редагувалося lucas-kane (29.12.2022 01:26:35)

Re: Порушення інкапсуляції

Олександр Ковальчук написав:

Для чого delete[ ] p[ i ] ?  там вказівник на об'єкт а не на масив.

Як саме ви цей об'єкт створюєте? Я щось його не бачу в коді.
Якщо це вказівник на об'єкт, тоді вказівник на вказівник зайвий.

Через те і питаю, як ви створювали цей об'єкт? За допомогою оператора new?

operator new, operator new[]
operator delete, operator delete[]

Re: Порушення інкапсуляції

lucas-kane написав:

Для чого delete[ ] p[ i ] ?  там вказівник на об'єкт а не на масив.

Як саме ви цей об'єкт створюєте? Я щось його не бачу в коді.

Його в коді немає бо питання було не про ініціалізацію масиву і видалення пам'яті...а про порушеня інкапсуляції.

13 Востаннє редагувалося koala (29.12.2022 01:25:02)

Re: Порушення інкапсуляції

У першому прикладі (доволі невдалому) подвійний вказівник не на двовимірний масив, а на масив поліморфних об'єктів. Але це не має вже значення, бо питання було в іншому.

Re: Порушення інкапсуляції

lucas-kane написав:
Олександр Ковальчук написав:

Доброго дня.
Діло в тому що в подальшому можна написати слідуюче:

MyClassArr marr(6);
baseAbstract * mclass = _arr.getMyClass(3);
delete mclass;

В останньому рядку було видалено елемент масиву.... а клас MyClassArr про це навіть не "здогадується"....

Це не так. Він не є елементом масиву.

По-перше, baseAbstract * mclass - буде створено у СТЕКУ.
По-друге,  baseAbstract * mclass = marr.getMyClass(3); присвоїть цій ділянці пам'яті адресу із КУПИ. В даному випадку 4-го  (4-1=3) елементу масиву вказівників - marr. Аж ніяк не буде навпаки.

Тут чесно кажучи нічого не зрозумів....

lucas-kane написав:

По-перше, baseAbstract * mclass - буде створено у СТЕКУ.

I?

lucas-kane написав:

По-друге,  baseAbstract * mclass = marr.getMyClass(3); присвоїть цій ділянці пам'яті адресу із КУПИ. В даному випадку 4-го  (4-1=3) елементу масиву вказівників - marr. Аж ніяк не буде навпаки.

???? Ну...так 0..1..2..3  четвертий елемент в масиві під індексом 3....а що не так?
І не адресу із купи а вказівник із масиву на виділену ділянку пам'яті...

Re: Порушення інкапсуляції

koala написав:

У першому прикладі (доволі невдалому) подвійний вказівник не на двовимірний масив, а на масив поліморфних об'єктів. Але це не має вже значення, бо питання було в іншому.

Саме так.
Вибачте за невдалий приклад.
Морочу голову людям.

Re: Порушення інкапсуляції

lucas-kane написав:
Олександр Ковальчук написав:

Для чого delete[ ] p[ i ] ?  там вказівник на об'єкт а не на масив.

Як саме ви цей об'єкт створюєте? Я щось його не бачу в коді.
Якщо це вказівник на об'єкт, тоді вказівник на вказівник зайвий.

Через те і питаю, як ви створювали цей об'єкт? За допомогою оператора new?

operator new, operator new[]
operator delete, operator delete[]

Вказівник не зайвий бо там масив абстрактного типу baseAbstract.
baseAbstract * marr = new baseAbstract[size]{} не буде працювати...не можливо створити масив абстрактних класів.