Причины определения неконстантных функций-членов get? - PullRequest
0 голосов
/ 08 декабря 2018

Я работаю над изучением C ++ с помощью книги Страуструпа (Принципы и практика программирования на C ++).В упражнении мы определяем простую структуру:

template<typename T>
struct S {
  explicit S(T v):val{v} { };

  T& get();
  const T& get() const;

  void set(T v);
  void read_val(T& v);

  T& operator=(const T& t); // deep copy assignment

private:
  T val;
};

Затем нас просят определить постоянную и неконстантную функцию-член, чтобы получить val.

Мне было интересно:Есть ли какой-то случай, когда имеет смысл иметь неконстантную get функцию, которая возвращает val?

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

Ответы [ 5 ]

0 голосов
/ 08 декабря 2018

Сначала позвольте мне перефразировать ваш вопрос:

Почему для участника используется неконстантный получатель, а не просто публичный член?

Несколько возможных причинпричины:

1.Простой в использовании инструмент

Кто сказал, что неконстантный геттер должен быть просто:

    T& get() { return val; }

?это может быть что-то вроде:

    T& get() { 
        if (check_for_something_bad()) {
            throw std::runtime_error{"Attempt to mutate val when bad things have happened");
        }
        return val;
    }

However, as @BenVoigt suggests, it is more appropriate to wait until the caller actually tries to mutate the value through the reference before spewing an error.

2.Культурное соглашение / «так сказал начальник»

Некоторые организации применяют стандарты кодирования.Эти стандарты кодирования иногда создаются людьми, которые могут быть чрезмерно оборонительными.Таким образом, вы можете увидеть что-то вроде:

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

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

3.Может быть, val просто нет?

Вы привели пример, в котором val на самом деле существует в экземпляре класса.Но на самом деле - это не обязательно!Метод get() может возвращать некоторый тип прокси-объекта, который при назначении, мутации и т. Д. Выполняет некоторые вычисления (например, сохранение или извлечение данных в базе данных).

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

Теперь, читая пункты 1 или 3, вы можете спросить "но у моего struct S есть val!"или "моим get() ничего интересного не делает!"- ну, правда, нет;но вы можете изменить это поведение в будущем.Без get() всем пользователям вашего класса потребуется изменить свой код.С get() вам нужно всего лишь внести изменения в реализацию struct S.

Теперь я не сторонник такого подхода к проектированию, но некоторые программисты делают.

0 голосов
/ 08 декабря 2018

Нет.Если получатель просто возвращает неконстантную ссылку на член, например, так:

private:
    Object m_member;

public:
    Object &getMember() {
         return m_member;
    }

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

Если вы позвоните getMember(), вы можете сохранить полученную ссылку науказатель / ссылка, и после этого вы можете делать все, что захотите, с помощью m_member, включающий класс ничего об этом не узнает.Это то же самое, как если бы m_member был общедоступным.

Обратите внимание, что если getMember() выполняет какую-то дополнительную задачу (например, он не просто возвращает m_member, но лениво создает его)тогда getMember() может быть полезно:

Object &getMember() {
    if (!m_member) m_member = new Object;
    return *m_member;
}
0 голосов
/ 08 декабря 2018

Неконстантные геттеры?

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

struct foo {
    int val() const { return val_; }
    int& val() { return val_; }
private:
    int val_;
};

, чтобы в зависимости от константы экземпляра вы получали ссылку или копию:

void bar(const foo& a, foo& b) {
    auto x = a.val(); // calls the const method returning an int
    b.val() = x;      // calls the non-const method returning an int&
};

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

В любом случае, более важно разработать интерфейс класса в соответствии с тем, что классдолжен делать и как вы хотите его использовать, а не слепо следовать соглашениям о методах установки и получения (например, вы должны дать методу осмысленное имя, которое выражает то, что он делает, а не только с точки зрения «притворяться инкапсулированным», а теперь предоставьте мне«Доступ ко всем вашим внутренним объектам через геттеры», что на самом деле означает использование геттеров везде).

Конкретный пример

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

struct my_array {
    int operator[](unsigned i) const { return data[i]; }
    int& operator[](unsigned i) { return data[i]; }
    private:
        int data[10];
};

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

non-Const справка из получить против инкапсуляции

1029 * Maybe не так очевидно, но это немного спорно, поддерживают ли обеспечивающие добытчик и сеттера инкапсуляцию или наоборот.Хотя в целом этот вопрос в значительной степени основан на мнениях, для получателей, которые возвращают неконстантные ссылки, речь идет не столько о мнениях.Они нарушают инкапсуляцию.Рассмотрим
struct broken {
    void set(int x) { 
        counter++;
        val = x;
    }
    int& get() { return x; }
    int get() const { return x; }
private:
    int counter = 0;
    int value = 0;
};

Этот класс не работает, как следует из названия.Клиенты могут просто получить ссылку, и у класса нет шансов подсчитать, сколько раз значение было изменено (как подсказывает set).После того, как вы вернете неконстантную ссылку, в отношении инкапсуляции будет мало разницы в том, чтобы сделать член открытым.Следовательно, это используется только для случаев, когда такое поведение является естественным (например, контейнер).

PS

Обратите внимание, что ваш пример возвращает const T&, а не значение,Это разумно для шаблонного кода, когда вы не знаете, сколько стоит копия, а для int вы не получите много, возвращая const int& вместо int.Для ясности я использовал не шаблонные примеры, хотя для шаблонного кода вы, скорее всего, скорее вернете const T&.

0 голосов
/ 08 декабря 2018

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

Один из реальных примеров - std::reference_wrapper.

0 голосов
/ 08 декабря 2018

get() вызывается неконстантными объектами, которым разрешено изменяться, вы можете сделать:

S r(0);
r.get() = 1;

, но если вы сделаете r const как const S r(0), строка r.get() = 1 noболее длительная компиляция, даже не для извлечения значения, поэтому вам нужна const-версия const T& get() const, чтобы хотя бы иметь возможность извлекать значение для const-объектов, что позволяет вам:

const S r(0)
int val = r.get()

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

...