Это плохая идея сделать метод класса, который передается переменным класса?

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
Если вы чувствуете, что делаете это, это намекает на плохой дизайн. Свойства класса, вероятно, принадлежат где-то еще.
битнофлогический
11
Фрагмент слишком маленький. Этот уровень смешанных абстракций не встречается в фрагментах такого размера. Вы также можете исправить это с помощью хорошего текста по соображениям дизайна.
Джошуа
16
Я, честно говоря, не понимаю, почему ты вообще это сделал. Что вы получаете? Почему бы просто не использовать addметод без аргументов, который напрямую работает с его внутренними компонентами? Просто почему?
Ян Кемп
2
@IanKemp Или есть addметод, который принимает аргументы, но не существует как часть класса. Просто чистая функция для добавления двух массивов вместе.
Кевин
Есть ли способ добавить всегда добавить arr1 и arr2, или она может быть использована , чтобы добавить что - либо к чему - либо?
user253751

Ответы:

33

В классе могут быть вещи, которые я бы сделал по-другому, но чтобы ответить на прямой вопрос, мой ответ был бы

да это плохая идея

Моя главная причина этого в том, что вы не можете контролировать то, что передается в addфункцию. Конечно, вы надеетесь, что это один из массивов-членов, но что произойдет, если кто-то передаст другой массив, размер которого меньше 100, или вы передадите длину больше 100?

Что происходит, так это то, что вы создали возможность переполнения буфера. И это плохо во всем.

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

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

  2. Если в классе уже есть переменные-члены, зачем вам их передавать? Это больше архитектурный вопрос.

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

Питер М
источник
Действительно, vector<int>было бы более подходящим; длина тогда будет бесполезна. В качестве альтернативы const int len, поскольку массив переменной длины не является частью стандарта C ++ (хотя некоторые компиляторы поддерживают его, потому что это дополнительная функция в C).
Кристоф
5
@Christophe Author использовал массив фиксированного размера, который должен быть заменен на std::arrayкоторый не затухает при передаче его параметрам, имеет более удобный способ получения его размера, копируется и имеет доступ с необязательной проверкой границ, но не требует дополнительных затрат Массивы в стиле C.
Куб
1
@Cubic: всякий раз, когда я вижу код, объявляющий массивы с произвольным фиксированным размером (например, 100), я почти уверен, что автор является новичком, который не имеет представления о последствиях этой ерунды, и это хорошая идея рекомендовать его / ее изменив этот дизайн на что-то с переменным размером.
Док Браун
Массивы в порядке.
Легкость гонки с Моникой
... для тех, кто не понял, что я имею в виду, см. также правило бесконечности ноль один
Док Браун
48

Вызов метода класса с некоторыми переменными класса не обязательно плох. Но делать это извне класса - очень плохая идея и предполагает фундаментальный недостаток в вашей ОО-структуре, а именно отсутствие правильной инкапсуляции :

  • Любой код, использующий ваш класс, должен знать, что lenэто длина массива, и использовать его последовательно. Это идет вразрез с принципом наименьшего знания . Такая зависимость от внутренних деталей класса чрезвычайно подвержена ошибкам и рискованна.
  • Это очень усложнит эволюцию класса (например, если однажды вы захотите изменить устаревшие массивы lenна более современные std::vector<int>), поскольку вам потребуется также изменить весь код, используя ваш класс.
  • Любая часть кода может нанести ущерб вашим MyClassобъектам, повредив некоторые публичные переменные без соблюдения правил (так называемые инварианты классов )
  • Наконец, метод в действительности не зависит от класса, так как он работает только с параметрами и не зависит от других элементов класса. Этот метод вполне может быть независимой функцией вне класса. Или хотя бы статический метод.

Вы должны рефакторинг вашего кода, и:

  • сделать ваши переменные класса privateили protected, если нет веских причин не делать этого.
  • Разработайте ваши публичные методы как действия, которые будут выполняться над классом (например:) object.action(additional parameters for the action).
  • Если после этого рефакторинга у вас все еще будут некоторые методы класса, которые нужно вызывать с переменными класса, сделайте их защищенными или закрытыми после проверки того, что они являются служебными функциями, поддерживающими открытые методы.
Christophe
источник
7

Если целью этой схемы является то, что вы хотите иметь возможность повторно использовать этот метод для данных, которые не поступают из этого экземпляра класса, тогда вы можете объявить его как staticметод .

Статический метод не принадлежит ни одному конкретному экземпляру класса. Это скорее метод самого класса. Поскольку статические методы не запускаются в контексте какого-либо конкретного экземпляра класса, они могут обращаться только к переменным-членам, которые также объявлены как статические. Учитывая, что ваш метод addне ссылается ни на одну из переменных-членов, объявленных в MyClass, он является хорошим кандидатом для получения объявления как статического.

Однако проблемы безопасности, упомянутые в других ответах, действительны: вы не проверяете, являются ли оба массива хотя бы lenдлинными. Если вы хотите написать современный и надежный C ++, вам следует избегать использования массивов. Используйте класс std::vectorвместо этого когда бы ни было возможно. В отличие от обычных массивов, векторы знают свой размер. Таким образом, вам не нужно отслеживать их размер самостоятельно. Большинство (не все!) Их методов также выполняют автоматическую проверку границ, которая гарантирует, что вы получите исключение, когда читаете или пишете после конца. Обычный доступ к массиву не выполняет проверку границ, что в лучшем случае приводит к segfault и может в худшем случае привести к уязвимости, связанной с переполнением буфера .

Philipp
источник
1

Метод в классе идеально манипулирует инкапсулированными данными внутри класса. В вашем примере нет никакой причины для метода add иметь какие-либо параметры, просто используйте свойства класса.

Рик Д
источник
0

Передача (часть) объекта в функцию-член - это нормально. При реализации своих функций вы всегда должны быть осведомлены о возможном алиасинге и его последствиях.

Конечно, контракт может оставить некоторые виды псевдонимов неопределенными, даже если язык их поддерживает.

Выдающиеся примеры:

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

Но у вашего примера другие проблемы:

  • Функция-член не использует ни своих привилегий, ни ссылки на них this. Он действует как бесплатная служебная функция, которая просто не в том месте.
  • Ваш класс не пытается представить какую-либо абстракцию . В соответствии с этим, он не пытается инкапсулировать свои внутренние объекты и не поддерживает никаких инвариантов . Это тупая сумка с произвольными элементами.

В итоге: да, этот пример плохой. Но не потому, что функция-член получает переданные объекты-члены.

Deduplicator
источник
0

В частности, это плохая идея по нескольким веским причинам, но я не уверен, что все они являются неотъемлемой частью вашего вопроса:

  • Наличие переменных класса (фактических переменных, а не констант) - это то, чего следует избегать, если это вообще возможно, и должно вызвать серьезное размышление о том, действительно ли вам это нужно.
  • Оставлять доступ на запись к этим переменным класса полностью открытым для всех - почти всегда плохо.
  • Ваш метод класса в конечном итоге не связан с вашим классом. На самом деле это функция, которая добавляет значения одного массива к значениям другого массива. Функция такого типа должна быть функцией в языке, у которого есть функции, и должна быть статическим методом «поддельного класса» (т. Е. Набора функций без данных или поведения объектов) в языках, которые не имеют функций.
  • Либо удалите эти параметры и превратите их в метод реального класса, но он все равно будет плохим по причинам, упомянутым ранее.
  • Почему массивы int? vector<int>сделает вашу жизнь проще
  • Если этот пример действительно представляет ваш дизайн, рассмотрите возможность размещения ваших данных в объектах, а не в классах, а также реализации конструкторов для этих объектов таким образом, чтобы не требовалось изменять значение данных объекта после создания.
Kafein
источник
0

Это классический подход «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
    }
}
Александр - Восстановить Монику
источник