Соглашения для методов доступа (геттеров и сеттеров) в C ++

Несколько вопросов о методах доступа в C ++ было задано на SO, но ни один из них не смог удовлетворить мое любопытство по этому поводу.

Я стараюсь по возможности избегать аксессоров, потому что, подобно Страуструпу и другим известным программистам, я считаю класс со многими из них признаком плохого объектно-ориентированного программирования. В C ++ я могу в большинстве случаев добавить больше ответственности к классу или использовать ключевое слово friend, чтобы избежать их. Однако в некоторых случаях вам действительно нужен доступ к определенным членам класса.

Есть несколько возможностей:

1. Ни в коем случае не используйте аксессоры

Мы можем просто сделать соответствующие переменные-члены общедоступными. Это недопустимо для Java, но, похоже, для сообщества C ++ это нормально. Однако меня немного беспокоят случаи, когда должна быть возвращена явная копия или ссылка на объект только для чтения (константная), это преувеличено?

2. Используйте методы get / set в стиле Java

Я не уверен, что это вообще с Java, но я имею в виду следующее:

int getAmount(); // Returns the amount
void setAmount(int amount); // Sets the amount

3. Используйте объективные методы get / set в стиле C.

Это немного странно, но, видимо, встречается все чаще:

int amount(); // Returns the amount
void amount(int amount); // Sets the amount

Чтобы это сработало, вам нужно будет найти другое имя для своей переменной-члена. Некоторые люди добавляют символ подчеркивания, другие - "m_". Мне тоже не нравится.

Какой стиль вы используете и почему?


person Community    schedule 05.09.2010    source источник
comment
Если оставить в стороне небольшие структуры, содержащие только данные, почему у вас сложилось впечатление, что общедоступные переменные-члены подходят для C ++? Обычно это тоже запрет.   -  person Georg Fritzsche    schedule 05.09.2010
comment
Да, это тоже недопустимо для C ++. Но если вы работали с MFC, можно было предположить, что все будет в порядке.   -  person vobject    schedule 05.09.2010
comment
Совсем недавно обсуждалось как [set / get методы в C ++] (stackoverflow.com/questions/3632533), которые я обнаружил с помощью serach средство установки получателя c ++. См. Также [Getter and setter, указатели или ссылки и хороший синтаксис для использования в c ++? ] (stackoverflow.com/questions/1596432), Стиль кодирования геттеров / сеттеров C ++ и, возможно, другие.   -  person dmckee --- ex-moderator kitten    schedule 05.09.2010
comment
См. эту статью (PDF), чтобы узнать, почему геттеры / сеттеры просто ошибаются .   -  person sbi    schedule 06.09.2010
comment
@Georg Fritzsche: Думаю, я видел это в STL и Boost, но на самом деле я не уверен. @dmckee: Да, на эту тему есть куча сообщений, и все они интересны, но ни один из них не отвечает на мои конкретные вопросы.   -  person Noarth    schedule 06.09.2010
comment
Я не понимаю, как вы добавляете много вопросов. Он все еще задает стилистический вопрос, который здесь уже задавали несколько раз, я уверен. Может быть, вы могли бы указать, где именно ваш вопрос вводит новшества? Кажется, вы даже не указываете, что вам может понадобиться только метод get, а не метод set или какой-либо другой вариант.   -  person n1ckp    schedule 06.09.2010
comment
Вам придется заставить меня писать общедоступные геттеры и сеттеры на C ++; они явно бесполезны на лице, если у них нет побочного эффекта.   -  person hoodaticus    schedule 01.06.2018
comment
@sbi Я много раз видел, как люди разглагольствуют о том, что это-не-ООП, и мне все еще трудно понять, что такое ООП , когда дело доходит до изменения состояния объекта = (PDF не проливает света на этот вопрос, в конце концов, о чем они все говорят?   -  person vines    schedule 11.06.2020
comment
@vines: В реальном объектно-ориентированном интерфейсе интерфейсы объекта (определенные в классе на статически типизированных языках) не должны иметь отношения к состоянию объекта, а только к операциям, которые предоставляет объект. То, что эти операции изменяют состояние объекта, является деталью реализации.   -  person sbi    schedule 12.07.2020


Ответы (10)


С моей точки зрения, поскольку я сижу с 4 миллионами строк кода C ++ (и это всего лишь один проект), с точки зрения обслуживания я бы сказал:

  • Можно не использовать геттеры / сеттеры, если члены неизменяемы (т.е. const) или простые без зависимостей (например, класс точки с членами X и Y).

  • Если член private, можно также пропустить геттеры / сеттеры. Я также считаю членов внутренних pimpl -классов как private, если модуль .cpp небольшой.

  • Если член public или protected (protected так же плохо, как public) и не const, непростой или имеет зависимости, тогда используйте геттеры / сеттеры.

Как специалист по обслуживанию, моя основная причина, по которой я хочу иметь геттеры / сеттеры, заключается в том, что тогда у меня есть место для размещения точек останова / ведения журнала / чего-то еще.

Я предпочитаю вариант 2, так как он более доступен для поиска (ключевой компонент при написании поддерживаемого кода).

person Community    schedule 06.09.2010
comment
Принятие конкретного ответа на вопросы сообщества вики всегда немного странно, потому что каждый ответ помогает. Тем не менее, я думаю, что ваш вариант лучше всего оправдан. В настоящее время я стараюсь по возможности избегать методов получения / установки. Чтобы придумать альтернативы, нужно время, но зачастую они лучше. Если мне нужны простые данные, упомянутые вами, я обычно использую структуру только для данных. - person Noarth; 08.10.2010
comment
Если вам нужно отлаживать аксессоры, это еще одна причина не использовать их. Наверняка есть более интересные места для установки точки останова. - person hoodaticus; 01.06.2018
comment
Это отличный момент, чтобы показать, что это полезно с точки зрения отладки, но я согласен, что, вероятно, есть альтернативные способы получения этой информации. Кроме того, я думаю, что GETS & SETS никогда не должны использоваться вместе. Многие предполагают, что GET / SET позволяет вам изменять возвращаемое значение, но тогда это не GET / SET, это метод, выполняющий некоторый набор действий, и его следует назвать полным намерением в данном процессе. - person Jeremy Trifilo; 09.01.2019

2) - лучший вариант ИМО, потому что он наиболее четко проясняет ваши намерения. set_amount(10) имеет большее значение, чем amount(10), и в качестве приятного побочного эффекта позволяет члену с именем amount.

Открытые переменные обычно - плохая идея, потому что нет инкапсуляции. Предположим, вам нужно обновить кеш или обновить окно при обновлении переменной? Жаль, если ваши переменные общедоступны. Если у вас есть метод set, вы можете добавить его туда.

person Community    schedule 05.09.2010
comment
Каноническая инкапсуляция означает сокрытие деталей внутреннего состояния и поведения логической единицы. Если вы обращаетесь к частному элементу данных через общедоступный интерфейс, то вы уже нарушаете инкапсуляцию, независимо от того, что вы делаете это с помощью средств доступа. Хорошо инкапсулированные классы не имеют средств доступа: вместо этого у них есть активные методы, которые используют и влияют на внутреннее состояние публично невидимым образом. - person ulidtko; 19.03.2012
comment
Что касается сценария измененных требований, например изменение кеша или обновление пользовательского интерфейса, или отправка сетевого запроса внутри вызова getId(): вставляя эту логику в геттер / сеттер, вы нарушаете контракт метода доступа. Таким образом вы не нарушите компиляцию клиентского кода, но, безусловно, нарушите его работу во время выполнения, что еще хуже. Потому что никто в здравом уме не предположит, что ваш геттер может, скажем, иногда выделять память или ваш сеттер может писать в файловую систему. Во всех этих сценариях вам потребуется перепроектировать в соответствии с новыми требованиями. - person ulidtko; 19.03.2012
comment
# 1 проясняет ваши намерения, а не # 2. №2 делает вид, будто инкапсулирует внутреннее состояние, делая переменную частной, а затем отменяет всю свою работу по сокрытию этого состояния, позволяя любому через общедоступные методы доступа читать и записывать эту переменную. №2 - это не случай ясных намерений, это просто игра в оболочку. - person hoodaticus; 01.06.2018

  1. Я никогда не использую этот стиль. Потому что это может ограничить будущее дизайна вашего класса, а явные геттеры или сеттеры так же эффективны с хорошими компиляторами.

    Конечно, в действительности встроенные явные геттеры или сеттеры создают такую ​​же базовую зависимость от реализации класса. Они просто уменьшают семантическую зависимость. Вам все равно придется все перекомпилировать, если вы их измените.

  2. Это мой стиль по умолчанию, когда я использую методы доступа.

  3. Мне этот стиль кажется слишком «умным». Я использую его в редких случаях, но только в тех случаях, когда я действительно хочу, чтобы средство доступа выглядело как переменная, насколько это возможно.

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

person Community    schedule 05.09.2010
comment
+1 для слишком умных. Я бы рекомендовал не злоупотреблять возможностью перегрузки, это только усложняет работу читателей, а также затрудняет поиск по кодовой базе. - person Matthieu M.; 06.09.2010
comment
-1 За ограничение будущего дизайна вашего класса и т. Д. Вы проектируете класс таким, какой он есть сейчас. Если вы захотите переделать его позже, сделайте это. Что, если это поле исчезнет в будущем? Аксессуар вам не поможет. И если вы сохраните его, вернув какое-то ненужное значение, у вас может быть тихая поломка, о которой вы не подозреваете. Не переусердствуйте, иначе вы могли бы просто получить карту средств доступа между именами полей и значениями и покончить с этим. - person einpoklum; 22.01.2016
comment
Почему вы хотите, чтобы метод доступа максимально походил на переменную? Это не переменные, зачем вам запутывать кого-то, думая, что это так? - person deetz; 20.07.2017
comment
@deetz - Что ж, свойство @ кажется довольно популярным в Python. И это способ невидимого преобразования доступа к переменной в вызов функции. - person Omnifarious; 20.07.2017

  1. Это хороший стиль, если мы просто хотим представить pure данные.

  2. Мне это не нравится :) потому что get_/set_ действительно не нужны, когда мы можем перегрузить их на C ++.

  3. STL использует этот стиль, например std::streamString::str и std::ios_base::flags, за исключением тех случаев, когда его следует избегать! когда? Когда имя метода конфликтует с именем другого типа, используется стиль get_/set_, например std::string::get_allocator из-за std::allocator.

person Community    schedule 06.09.2010
comment
Боюсь, что в этом отношении STL может быть немного непоследовательным. К сожалению, сам Страуструп, похоже, не высказал мнения о том, как делать геттеры / сеттеры, за исключением того, что ему не нравятся классы со многими из них. - person Noarth; 06.09.2010

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

При этом, если такой дизайн нуждается в рефакторинге и доступен исходный код, я бы предпочел использовать шаблон Visitor Design. Причина:

а. Это дает классу возможность решить, кому разрешить доступ к его частному состоянию.

б. Это дает классу возможность решить, какой доступ разрешить каждой из организаций, которые заинтересованы в ее частном состоянии.

c. Он четко документирует такой внешний доступ через понятный интерфейс класса.

Основная идея:

а) Редизайн, если возможно, иначе,

б) Рефакторинг так, чтобы

  1. Весь доступ к состоянию класса осуществляется через хорошо известный индивидуальный интерфейс.

  2. Должна быть возможность настроить какие-то действия и запреты для каждого такого интерфейса, например весь доступ из внешнего объекта ХОРОШО должен быть разрешен, весь доступ из внешнего объекта ПЛОХО должен быть запрещен, а внешнему объекту ОК должно быть разрешено получить но не установлен (например)

person Community    schedule 06.09.2010
comment
Интересный момент, я не знаю, почему это было отклонено. Но я бы предпочел ключевое слово friend, если конкретному классу потребуется доступ к определенным членам. - person fhd; 23.09.2010

  1. Я бы не исключал аксессуаров из использования. Может для некоторых структур POD, но я считаю их хорошей вещью (некоторые аксессоры тоже могут иметь дополнительную логику).

  2. Соглашение об именах не имеет значения, если вы единообразны в своем коде. Если вы используете несколько сторонних библиотек, они в любом случае могут использовать разные соглашения об именах. Так что дело вкуса.

person Community    schedule 05.09.2010

Я видел идеализацию классов вместо интегральных типов для обозначения значимых данных.

Что-то вроде этого ниже обычно не позволяет эффективно использовать свойства C ++:

struct particle {
    float mass;
    float acceleration;
    float velocity;
} p;

Почему? Потому что результатом p.mass * p.acceleration является float, а не форсировка, как ожидалось.

Определение классов для обозначения цели (даже если это значение, например amount, упомянутое ранее) имеет больше смысла и позволяет нам делать что-то вроде:

struct amount
{
    int value;

    amount() : value( 0 ) {}
    amount( int value0 ) : value( value0 ) {}
    operator int()& { return value; }
    operator int()const& { return value; }
    amount& operator = ( int const newvalue )
    {
        value = newvalue;
        return *this;
    }
};

Вы можете получить доступ к значению суммы неявно с помощью оператора int. Более того:

struct wage
{
    amount balance;

    operator amount()& { return balance; }
    operator amount()const& { return balance; }
    wage& operator = ( amount const&  newbalance )
    {
        balance = newbalance;
        return *this;
    }
};

Использование Getter / Setter:

void wage_test()
{
    wage worker;
    (amount&)worker = 100; // if you like this, can remove = operator
    worker = amount(105);  // an alternative if the first one is too weird
    int value = (amount)worker; // getting amount is more clear
}

Это другой подход, не значит, что он хороший или плохой, это другой подход.

person Community    schedule 20.07.2017

Дополнительная возможность может быть:

int& amount();

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

str.length() = 5; // Ok string is a very bad example :)

Иногда это может быть просто хороший выбор:

image(point) = 255;  

Еще одна возможность - использовать функциональную нотацию для изменения объекта.

edit::change_amount(obj, val)

Таким образом, опасную функцию редактирования можно вынести в отдельное пространство имен с собственной документацией. Кажется, что это естественно для общего программирования.

person Community    schedule 05.09.2010
comment
Я не голосовал за это, но самый первый ответ - очень плохой ответ, потому что он раскрывает детали дизайна, которые обычный аксессуар скрывает. И другого предложения недостаточно, чтобы оправдать ответ. Если вы собираетесь попробовать что-то вроде первого стиля, по крайней мере, верните какой-нибудь объект с operator = вместо ссылки на внутренний член данных. - person Omnifarious; 06.09.2010
comment
Чтобы прояснить ситуацию, мой ответ только завершает список возможностей, которые начал OP (возможно, у меня должен быть комментарий). Я не особо рекомендую эти возможности. Как сказал Omnifarious, использование прокси-класса с перегрузкой operator= в первом случае, как минимум, является хорошим делом. Я бы использовал второй вариант в некоторых случаях, если я хочу обеспечить функциональность редактирования поперек иерархии классов, и когда необходимо четко отделить эту функциональность от класса. Хотя для простого сеттера это, вероятно, плохая идея. - person log0; 06.09.2010

Позвольте мне рассказать вам об одной дополнительной возможности, которая кажется наиболее осознанной.

Необходимо прочитать и изменить

Просто объявите эту переменную общедоступной:

class Worker {
public:
    int wage = 5000;
}

worker.wage = 8000;
cout << worker.wage << endl;

Нужно просто прочитать

class Worker {
    int _wage = 5000;
public:
    inline int wage() {
        return _wage;
    }
}

worker.wage = 8000; // error !!
cout << worker.wage() << endl;

Обратной стороной этого подхода является то, что вам нужно изменить весь вызывающий код (то есть добавить круглые скобки), когда вы хотите изменить шаблон доступа.

person Community    schedule 15.02.2014

вариант №3, мне сказали, что это может быть «беглый» стиль

class foo {
  private: int bar;
  private: int narf;
  public: foo & bar(int);
  public: int bar();
  public: foo & narf(int);
  public: int narf();
};

//multi set (get is as expected)
foo f; f.bar(2).narf(3);
person Community    schedule 23.07.2020
comment
Это нормально с точки зрения функциональности, но несколько выходит за рамки вопроса. По сути, это геттер / сеттер в стиле C # с возможностью связывания наборов. Я слышал, что этот узор не часто используется, и я знаю, что его, безусловно, легко заменить. Вопрос также требует объяснения, почему вы используете этот шаблон по сравнению с другими, если вы хотите его указать. - person Matias Chara; 24.07.2020
comment
Примечание: вам не нужно повторять частные и общедоступные перед каждым полем / методом. Поместите всех закрытых участников после закрытого: и всех общедоступных участников после общедоступного: - person Barnack; 09.03.2021