Подтвердить что ты не робот

Возвращает ли ссылка на аксессуар идиоматику?

В С++ можно создать аксессор, который возвращает ссылку на частное поле.

class Cls {
    private:
        int _attr;
    public:
        int& attr() { return _attr; }
};

чтобы атрибут мог быть доступен как таковой:

// set
c.attr() = 4;

// get
cout << c.attr() << endl;

Является ли этот стиль аксессуарами идиоматической/хорошей практикой? Станет ли средний программист на С++ удивлен, увидев этот стиль доступа? (Подсказка: я был удивлен в первый раз, когда увидел это, но вроде понравился стиль)

4b9b3361

Ответ 1

Нет, этот код будет неудивительным, если вы также обеспечиваете перегрузку const:

class Cls {
    int _attr;
public:
    int& attr() { return _attr; }
    int const& attr() const { return _attr; }
};

Однако я рассмотрел бы следующую идиоматику по причинам, упомянутым Крисом Лутцем и Марком Б:

class Cls {
    int _attr;
public:
    int const& attr() const { return _attr; }
    void attr(int i) { _attr = i; }
};

Ответ 2

Предположим, что вам нужны гарантии:

template <int Base>
class Digit {
  private:
    int attr_; // leading underscore names are reserved!
  public:
    int attr() const { return attr_; }
    void attr(int i) { attr_ = i % Base; } // !
};

В этом случае вы не можете вернуть ссылку, так как нет способа гарантировать, что пользователь не будет изменять ее таким образом, который вы не хотите. Это основная причина использования сеттера - вы можете (сейчас или на более поздний срок) фильтровать вход, чтобы убедиться в его приемлемости. Со ссылкой вы должны слепо принять то, что пользователь назначает вашему участнику. В этот момент почему бы просто не сделать это публично?

Ответ 3

В общем случае для нетривиальных классов (например, те, которые могут быть определены просто как struct), предоставляя аксессурам/мутаторам определенные атрибуты, вероятно, является запахом кода. Как правило, классы должны иметь тенденцию сохранять свое внутреннее состояние именно так: Внутреннее. Если вы предоставляете неконстантную ссылку на внутреннее состояние, то внезапно у вас вообще нет контроля над инвариантами класса. Это не только значительно усложнит процесс отладки, так как масштаб возможных изменений состояния - это весь проект, который не позволяет вам постоянно изменять внутренности вашего класса, потому что они на самом деле являются как государственным, так и пользовательским API.

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

Ответ 4

Выполнение этого полностью побеждает цель сделать его приватным в первую очередь.

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

int  attr() const {return _attr;}
void attr(int a)  {_attr = a;}

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

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

Ответ 5

Это зависит. Если роль класса состоит в том, чтобы содержать такие объекты (например, контейнерный класс), то это очень идиоматично, и нормальный способ делать вещи. Однако в большинстве других случаев считается предпочтительным использовать методы getter и setter. Не обязательно называются getXxx и setXxx --- наиболее часто встречающееся соглашение об именах, которое я видел, использует m_attr для имя атрибута и просто attr для имени и геттер и сеттер. (Перегрузка операторов будет выбирать между ними в соответствии с количеством аргументов.)

- Джеймс Канзе

Ответ 6

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

Ответ 7

Это относительно частый, но плохой стиль.

Если вы посмотрите на STL, например, вы заметите, что класс, который приводит к внутреннему состоянию const& или &, обычно является обычным контейнером и предоставляет только такой вид доступа для того, что вы на самом деле храните в них. У вас, очевидно, нет возможности изменять непосредственно такие атрибуты, как size или внутренние узлы двоичного дерева.

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

Насколько я понимаю, это два типа классов в С++:

struct BigBag {
  Foo _foo;
  Bar _bar;
  FooBar _foobar;
};

class MyClass {
public:
  explicit MyClass(int a, int b);

  int getSomeInfo();

  void updateDetails(int a, int b);
private:
  // no peeking
};

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

Ответ 8

На всякий случай вы хотите избавиться от круглых скобок...

class Cls {
    private:
        int _attr;
    public:
        Cls() : attr(_attr) { }
        int& attr;
};

Изменить: точка Криса - хорошая. По сути, ничего не получается от обертывания частной переменной общедоступной ссылкой. Следующая версия добавляет что-то. Создание ссылки const предотвращает настройку, но позволяет получить приватную переменную.

class Cls {
    private:
        int _attr;
    public:
        Cls() : attr(_attr) { }
        int const & attr;
};