
Контекст
Когда вы создаете класс, вам часто приходится реализовывать методы доступа к одному или нескольким атрибутам этого класса для того, чтобы можно было редактировать их.
Сделать это можно двумя способами:
С помощью сеттера, метода, который принимает новое значение атрибута в качестве аргумента.
С помощью геттера-ссылки (reference-getter), метода, который возвращает ссылку на сам атрибут.
Вот небольшой пример, показывающий, как с помощью двух этих методов получить доступ к полю bar.
class Foo { int m_bar; public: //Сеттер void set_bar(int bar) { m_bar = bar; } //Геттер-ссылка int & get_bar() { return m_bar; } }; int main() { Foo foo; //Редактирование через сеттер foo.set_bar(42); //Редактирование с помощью геттер-ссылки foo.get_bar() = 84; return 0; }
Некоторые могут поспорить со мной, заявив что для этого есть и другие методы, но я остаюсь верен убеждению, что это всего лишь вариации сеттера или геттер-ссылки.
Сеттер
Сеттер по отношению к классу является интерфейсом только для записи (write-only). Вы указываете значение, и соответственно обновляете класс.
Зачастую он более или менее напрямую обновляет атрибут, копируя/перемещая(move) предоставленные вами данные.
Примеры
// Самый примитивный сеттер void set_foo(int foo) { m_foo = foo; }
// Сеттер, который делает проверку перед присваиванием void set_foo(Foo foo) { if (foo.is_valid()) m_foo = foo; }
// move-сеттер void set_big_foo(BigFoo && big_foo) { m_big_foo = std::forward<BigFoo>(big_foo); }
Геттер-ссылка
Геттер-ссылка (reference-getter) представляет собой метод, который возвращает ссылку на атрибут, что дает непосредственный доступ к нему, в том числе предоставляя возможность его редактирования.
Этот метод особенно полезен для атрибутов-объектов для того, чтобы вы могли вызывать неконстантные методы непосредственно на них.
Примеры
// Here is the implementation of the reference-getter Foo & MyClass::get_foo() { return m_foo; } // ... // Used to edit an attribute myClass.getFoo().bar = 42; // Used to call a non-const method myClass.getFoo().udpate();
Какой метод выбрать?
Сделать выбор станет довольно просто, как только вы поймете разницу между ними.
Когда вам нужно пересоздать данные и поместить их на место уже существующих, вам пригодится сеттер. Он также подходит для редактирования простых данных (целые числа, значения чисел с плавающей запятой, указатели и т. д.) или если вам нужно подставить полностью новое значение. Еще один вариант использования сеттеров: когда вы намеренно хотите дать пользователю возможность только редактировать данные, но не читать.
Геттер-ссылка вам пригодится для редактирования данных атрибута (а не атрибута целиком). Часто нам необходимо отредактировать только часть атрибута или вызвать для них неконстантный метод.
Другими словами, сеттер заменяет атрибут, а геттер-ссылка редактирует атрибут.
Пример
Рассмотрим следующий код:
#include <vector> using namespace std; struct Item { bool validity; int value; }; class Foo { public: Foo(size_t size) : m_max_size(size), m_data(size, {true, 0}) {} void set_max_size(size_t max_size) { m_max_size = max_size; } Item & get_item(size_t index) { return m_data.at(index); } size_t get_data_size() const { return m_data.size(); } private: bool m_max_size; std::vector<Item> m_data; }; static void set_foo_size(Foo & foo, size_t new_size) { foo.set_max_size(new_size); for (size_t i = new_size ; i < foo.get_data_size() ; ++i) foo.get_item(i).validity = false; }
В коде представлен простой класс, который содержит набор данных (Item’ы). Эти данные могут быть валидными или невалидными (true - валидные, false - невалидные).
Затем мы реализуем небольшую функцию, которая изменяет размер коллекции, не удаляя никаких элементов. Элементы, выходящие за заданный размер, становятся невалидными.
Мы задаем значение m_max_size с помощью сеттера, так как это обычное целое число, значение которого меняется при изменении размера коллекции.
С помощью геттер-ссылки мы можем получить доступ к каждому Item‘у из m_data, так как нам не всегда требуется полностью удалять item, иногда нам необходимо просто отредактировать его часть.
Альтернатива
Мы могли бы редактировать валидность, используя более специфичный сеттер, например:
class Foo { // ... void set_item_validity(size_t index, bool validity) { m_data.at(index).validity = validity; } // ... };
Сделав так, мы бы потеряли возможность редактировать value Item,поэтому это решение полностью зависит от деталей вашей реализации.
Однако, пожалуйста, помните, что писать сеттеры и для value и для validity является плохой практикой. Делать это в корзине данных с двумя атрибутами вряд ли будет правильным решением, потому что как только ваша реализация начнет расти, ваша кодовая база будет переполнена бесполезными аксессорами. Вам нужен полный доступ? Так и реализуйте полный доступ.
Заключение
Многие из вас посчитают эту тему тривиальной, но многие разработчики все еще путаются, когда следует использовать сеттер, а когда геттер-ссылку. Будьте внимательны.
Материал подготовлен в рамках специализации «C++ Developer».
Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем специализации — приглашаем на день открытых дверей онлайн. Регистрация здесь.
