Це погана ідея зробити метод класу, який передається змінним класом?


14

Ось що я маю на увазі:

class MyClass {
    int arr1[100];
    int arr2[100];
    int len = 100;

    void add(int* x1, int* x2, int size) {
        for (int i = 0; i < size; i++) {
            x1[i] += x2[i];
        }
    }
};

int main() {
    MyClass myInstance;

    // Fill the arrays...

    myInstance.add(myInstance.arr1, myInstance.arr2, myInstance.len);
}

addВи можете вже отримати доступ до всіх необхідних йому змінних, оскільки це метод класу, тож це погана ідея? Чи є причини, чому я повинен чи не повинен цього робити?


13
Якщо ви хочете зробити це, це натякає на поганий дизайн. Властивості класу, ймовірно, належать десь ще.
bitsoflogic

11
Знімок занадто малий. Цей рівень змішаних абстракцій не трапляється в фрагментах такого розміру. Ви також можете це виправити за допомогою хорошого тексту з міркувань щодо дизайну.
Джошуа

16
Я, чесно кажучи, не розумію, чому ви це зробили. Що ви отримуєте? Чому б просто не мати addметод без аргументів, який безпосередньо працює на внутрішніх місцях? Тільки чому?
Ян Кемп

2
@IanKemp Або є addметод, який бере аргументи, але не існує як частина класу. Просто чиста функція для додавання двох масивів разом.
Кевін

Чи додає метод завжди додавання arr1 і arr2, або його можна використовувати, щоб додати щось до чого-небудь?
користувач253751

Відповіді:


33

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

так, це погана ідея

Моя головна причина цього в тому, що ви не маєте контролю над тим, що передано addфункції. Впевнені, що ви сподіваєтесь, що це один із масивів-членів, але що станеться, якщо хтось проходить в інший масив, який має менший розмір, ніж 100, або ви проходите довжиною більше 100?

Що відбувається, це те, що ви створили можливість перевищення буфера. І це все погано.

Щоб відповісти на ще кілька (для мене) очевидних питань:

  1. Ви змішуєте масиви стилів C із C ++. Я не гуру C ++, але я знаю, що C ++ має кращі (безпечніші) способи обробки масивів

  2. Якщо в класі вже є змінні члена, навіщо вам їх передавати? Це більше архітектурне питання.

Інші люди з більшим досвідом C ++ (я припинив його використовувати 10 чи 15 років тому), можливо, мають красномовніші способи пояснення проблем, і, ймовірно, також з’являться більше питань.


Дійсно, vector<int>було б більш придатним; довжина тоді була б марною. Крім того const int len, оскільки масив змінної довжини не є частиною стандарту C ++ (хоча деякі компілятори підтримують його, оскільки це додаткова функція в C).
Крістоф

5
@Christophe Автор використовував масив фіксованого розміру, який слід замінити, std::arrayякий не розпадається при передаванні його до параметрів, має більш зручний спосіб отримати його розмір, копіюється і має доступ з додатковою перевіркою меж, не маючи накладних витрат Масиви стилю C
Кубічний

1
@Cubic: коли я бачу код, що оголошує масиви з довільним фіксованим розміром (наприклад, 100), я майже впевнений, що автор - початківець, який не має уявлення про наслідки цієї дурниці, і це гарна ідея рекомендувати йому / їй змінивши цей дизайн на щось із змінним розміром.
Док Браун

Масиви прекрасні.
Гонки легкості з Монікою

... для тих, хто не зрозумів, що я маю на увазі, дивіться також Правило нуля однієї нескінченності
Doc Brown

48

Виклик методу класу з деякими змінними класу не обов'язково поганий. Але робити це поза класом - дуже погана ідея і говорить про принциповий недолік у вашому проекті OO, а саме на відсутність належної інкапсуляції :

  • Будь-який код, який використовує ваш клас, повинен знати, що lenце довжина масиву, і використовувати його послідовно. Це суперечить принципу найменшого знання . Така залежність від внутрішніх деталей класу надзвичайно схильна до помилок та ризикована.
  • Це зробило б еволюцію класу дуже складною (наприклад, якщо одного дня ви хочете змінити застарілі масиви та lenна більш сучасні std::vector<int>), оскільки це вимагатиме від вас також змінити весь код за допомогою свого класу.
  • Будь-яка частина коду може спричинити хаос у ваших MyClassоб'єктах, пошкодивши деякі загальнодоступні змінні без дотримання правил (так звані класові інваріанти )
  • Нарешті, метод насправді не залежить від класу, оскільки він працює лише з параметрами і не залежить від іншого елемента класу. Цей вид методу цілком може бути самостійною функцією поза класом. Або хоча б статичним методом.

Вам слід повторно змінити код і:

  • зробити свої змінні класу privateабо protected, якщо немає вагомих причин цього не робити.
  • спроектуйте свої публічні методи як дії, які слід виконувати на уроці (наприклад:) object.action(additional parameters for the action).
  • Якщо після цього рефакторингу, у вас все ще будуть деякі методи класу, які потрібно викликати зі змінними класу, зробити їх захищеними або приватними після того, як перевірили, що вони є функціями утиліти, що підтримують публічні методи.

7

Якщо мета цієї конструкції полягає в тому, що ви хочете мати можливість повторно використовувати цей метод для даних, які не надходять з цього примірника класу, тоді ви можете оголосити його як staticметод .

Статичний метод не належить до жодного окремого екземпляра класу. Це скоріше метод самого класу. Оскільки статичні методи не запускаються в контексті будь-якого конкретного примірника класу, вони можуть отримати доступ лише до змінних членів, які також оголошені статичними. Зважаючи на те, що ваш метод addне посилається ні на одну зі змінних членів, оголошених в MyClass, це хороший кандидат для отримання оголошених статичними.

Однак проблеми безпеки, згадані іншими відповідями, є дійсними: Ви не перевіряєте, чи обидва масиви принаймні lenдовгі. Якщо ви хочете написати сучасний та надійний C ++, тоді вам слід уникати використання масивів. std::vectorЗамість цього використовуйте клас, коли це можливо. На відміну від регулярних масивів, вектори знають власний розмір. Тому вам не потрібно самостійно стежити за їх розмірами. Більшість (не всі!) Їх методів також роблять автоматичну перевірку меж, що гарантує, що ви отримаєте виняток, коли читаєте чи пишете минуле кінця. Регулярний доступ до масиву не здійснює перевірку зв'язаних даних, що призводить до кращого рівня сегмента та може спричинити вразливу вразливість переповнення буфера .


1

Метод у класі ідеально маніпулює інкапсульованими даними всередині класу. У вашому прикладі немає причини, щоб у методі add не було параметрів, просто використовуйте властивості класу.


0

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

Звичайно, контракт може залишати невизначені певні типи, навіть якщо мова підтримує це.

Видатні приклади:

  • Самопризначення. Це повинно бути, можливо, дорогим, не-оп. Не песимізуйте загальну справу щодо цього.
    З іншого боку, присвоєння саморуху, хоча воно не повинно вибухнути перед вашим обличчям, не повинно бути неоперативним.
  • Вставлення копії елемента в контейнер до контейнера. Щось подібне v.push_back(v[2]);.
  • std::copy() припускає, що призначення не починається всередині джерела.

Але ваш приклад має різні проблеми:

  • Функція-член не використовує жодних своїх привілеїв і не посилається на них this. Він діє як безкоштовна утиліта-функція, яка просто знаходиться в неправильному місці.
  • Ваш клас не намагається представляти якісь абстракції . Відповідно до цього, він не намагається інкапсулювати свої нутрощі, а також не підтримувати інваріантів . Це німий мішок довільних елементів.

Підсумовуючи це: Так, цей приклад поганий. Але не тому, що функція member отримує передані об'єкти member.


0

Зокрема, це погана ідея з кількох поважних причин, але я не впевнений, що всі вони є невід'ємною частиною вашого питання:

  • Мати змінні класу (фактичні змінні, а не константи) - це те, чого взагалі можна уникати, і це може викликати серйозне роздуми над тим, чи потрібні вони вам абсолютно.
  • Залишити доступ до запису до цих змінних класів повністю відкритим для всіх майже загально.
  • Ваш метод класу не пов'язаний з вашим класом. Що це насправді - це функція, яка додає значення одного масиву до значень іншого масиву. Цей вид функції повинен бути функцією на мові, яка має функції, і повинен бути статичним методом "фальшивого класу" (тобто набору функцій без даних або поведінки об'єкта) в мовах, які не мають функцій.
  • Як альтернативу, видаліть ці параметри та перетворіть їх на метод справжнього класу, але це все одно буде погано з причин, зазначених раніше.
  • Чому int масиви? vector<int>полегшило б ваше життя.
  • Якщо цей приклад справді є репрезентативним для вашої конструкції, розгляньте, як розмістити свої дані в об'єктах, а не в класах, а також реалізуйте конструктори для цих об'єктів таким чином, що не потрібно змінювати значення даних об'єкта після створення.

0

Це класичний підхід "C з класами" до C ++. Насправді це не те, про що писав би будь-який досвідчений програміст C ++. Для одного, використання сировинних масивів C є загалом непогашеним, якщо ви не реалізуєте бібліотеку контейнерів.

Щось подібне було б доречніше:

// Don't forget to compile with -std=c++17
#include <iostream>
using std::cout; // This style is less common, but I prefer it
using std::endl; // I'm sure it'll incite some strongly opinionated comments

#include <array>
using std::array;

#include <algorithm>

#include <vector>
using std::vector;


class MyClass {
public: // Begin lazy for brevity; don't do this
    std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
    std::array<int, 5> arr2 = {10, 10, 10, 10, 10};
};

void elementwise_add_assign(
    std::array<int, 5>& lhs,
    const std::array<int, 5>& rhs
) {
    std::transform(
        lhs.begin(), lhs.end(), rhs.begin(),
        lhs.begin(),
        [](auto& l, const auto& r) -> int {
            return l + r;
        });
}

int main() {
    MyClass foo{};

    elementwise_add_assign(foo.arr1, foo.arr2);

    for(auto const& value: foo.arr1) {
        cout << value << endl; // arr1 values have been added to
    }

    for(auto const& value: foo.arr2) {
        cout << value << endl; // arr2 values remain unaltered
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.