Возвращает ли ссылка на аксессор idiomatic? - PullRequest
12 голосов
/ 09 марта 2011

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

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

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

// set
c.attr() = 4;

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

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

Ответы [ 8 ]

10 голосов
/ 09 марта 2011

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

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; } // !
};

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

7 голосов
/ 09 марта 2011

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

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

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

class Cls {
    int _attr;
public:
    int const& attr() const { return _attr; }
    void attr(int i) { _attr = i; }
};
5 голосов
/ 09 марта 2011

Это полностью отрицает цель сделать его приватным.

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

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

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

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

5 голосов
/ 09 марта 2011

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

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

4 голосов
/ 09 марта 2011

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

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

2 голосов
/ 09 марта 2011

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

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

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

Насколько мне известно, это два типа классов в C ++:

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, потому что в нем много клиентского кода, в этом случае вы не раскрываете подробности реализации.

2 голосов
/ 09 марта 2011

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

0 голосов
/ 09 марта 2011

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

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

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

class Cls {
    private:
        int _attr;
    public:
        Cls() : attr(_attr) { }
        int const & attr;
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...