Контекст
Когда вы создаете класс, вам часто приходится реализовывать методы доступа к одному или нескольким атрибутам этого класса для того, чтобы можно было редактировать их.
Сделать это можно двумя способами:
С помощью сеттера, метода, который принимает новое значение атрибута в качестве аргумента.
С помощью геттера-ссылки (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».
Если вам интересно узнать подробнее о формате обучения и программе, познакомиться с преподавателем специализации — приглашаем на день открытых дверей онлайн. Регистрация здесь.
Комментарии (7)
Ryppka
03.09.2021 14:58+2Хм, никогда бы не пришло в голову называть метод, возвращающий неконстантную ссылку "getter".
saboteur_kiev
03.09.2021 16:12Вся суть и геттера и сеттера в том, что его можно в любой момент переписать так, как это нужно в текущее время разработки проекта, и при этом сохранить совместимость, чтобы не поломать зависимости.
Надо изменить типа данных аттрибут c integer на double - меняем аттрибут, подправляем геттер/сеттер чтобы он для зависимостей делал вид что это все еще integer, пишем новую пару методов геттер/сеттер с другим именем, но которые уже полноценно возвращают/принимают double и все зависимости спокойно, без спешки переписываем.
Лучшие и худшие методы? Ну я не очень понимаю - в каждом конкретном случае используй то, что тебе просто удобно.
NightShad0w
03.09.2021 16:18+3К переводу претензий нет, но вот выбор оригинала сомнителен. В оригинальной статье мухи с котлетами перемешаны. Не стоит принципы доступа к полям объяснять через типы значений.
Когда вам нужно пересоздать данные и поместить их на место уже существующих, вам пригодится сеттер. Он также подходит для редактирования простых данных (целые числа, значения чисел с плавающей запятой, указатели и т. д.) или если вам нужно подставить полностью новое значение. Еще один вариант использования сеттеров: когда вы намеренно хотите дать пользователю возможность только редактировать данные, но не читать.
Суть и назначение сеттеров — сохранение и поддержание инварианты класса. Целые ли числа, абстракция ли наивысшего порядка значения не имеет. Нужен инвариант — нужен сеттер. Не нужен инвариант — прямой доступ к полю сэкономит символы и строки кода.
Особым случаем можно считать сеттеры как элемент абстрактного интерфейса, где за сеттером прячется нечто более многоступенчатое с возможно разными и независимыми реализациями.Геттер-ссылка вам пригодится для редактирования данных атрибута (а не атрибута целиком). Часто нам необходимо отредактировать только часть атрибута или вызвать для них неконстантный метод.
Геттер-ссылка — это в принципе две независимые сущности. Геттер собственно класса, и тип возвращаемого значения. Почему-то не упомянуты Геттер-значение, Геттер-конст-ссылка, геттер-конст-значение, конст-геттер-*, не-конст-геттер-*.
А суть опять же не в том, что геттер делает, а в том, зачем его использовать. Семантика ссылок в С++ позволяет использовать null-safe виртуальщину, когда вызывающий объект видит только ссылку на интерфейс, но может подменять реализации по необходимости, и инкапсулирующий объект, используя абстрактный интерфейс, не требует изменения собственного алгоритма. Так реализуется паттерн Стратегия, когда элементы стратегии сами являются подстратегиями. И из дефолтного состояния, объект можно частично доконфигурировать подстратегиями.
LLVM использует 'геттер-ссылки' для конфигурирования виртуальной машины и кодогенератора.
NeoCode
03.09.2021 21:10-1Почему-то до сих пор в язык не ввели встроенные геттеры и сеттеры. Хотя например уже очень давно такое было в C++Builder (__property), у Microsoft (__declspec(property)). А вот в GCC такой возможности нет, хотя казалось бы — именно GCC как никакой другой компилятор богат на языковые расширения.
dvserg
27.09.2021 12:05Это не описывается стандартом, и всего лишь расширения конкретного производителя. В Борланде это было введено для возможностей визуального программирования.
dvserg
Для readonly доступа к приватным членам где-то встречал такой варинат:
Gorthauer87
А что с ним случится в случае с мувами и прочими переприсваиваниями?