Какой метод лучше для реализации get/set?
Есть два метода для реализации get/set.
Способ 1:
Определить get и установить отдельно.
class my_class
{
// ...
};
class main_class
{
public:
my_class get_data() const
{
return m_data;
}
void set_data(my_class value)
{
m_data = value;
}
private:
my_class m_data;
};
Примечание. В этом методе get выполняется достаточно быстро: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value
И еще один метод (метод 2):
Определите два тела получения: первый const и другой не const.
class my_class
{
// ...
};
class main_class
{
public:
const my_class& get_data() const
{
return m_data;
}
my_class& get_data() // Works like set.
{
return m_data;
}
private:
my_class m_data;
};
Используя эти методы:
void main()
{
main_class cls;
// For method 1.
my_class data;
data = cls.get_data();
cls.set_data(data);
// For method 2.
const my_class data1;
my_class data2;
data1 = cls.get_data(); // const get invoked.
cls.get_data() = data2; // Like set beacuase non const get invoked.
}
Мой вопрос, какой из этих методов для реализации get/set лучше?
Знаете ли вы лучший метод?
Edit:
Для ответов, которые верят, что метод 1 лучше, что вы скажете в ситуации ниже:
void main()
{
main_class cls;
// For method 1.
cls.get_data().do_something_else(); // Not effictive for cls, because data losts.
// For method 2.
cls.get_data().do_something_else(); // Effictive for cls.
}
Ответы
Ответ 1
Вы всегда должны использовать ссылку для любых пользовательских классов для передачи только адреса, а не класса значений. Вы также должны избегать передачи не-const ссылки для редактирования. Ниже приведены мои рекомендации.
class my_class
{
// ...
};
class main_class
{
public:
const my_class & get_data() const
{
return m_data;
}
void set_data(const my_class & data)
{
m_data = data;
}
private:
my_class m_data;
};
Ответ 2
Я знаю, что это не будет популярным ответом с пуристами С++, и до того, как я узнаю Python, и Ruby я бы не предложил эту возможность... но... Поскольку получатель и сеттер, которые вы предоставили, не делают проверка диапазона или специальные расчеты, почему бы не сделать его общедоступным?
class main_class
{
public:
my_class my_data;
}
Конечно, вы потеряете const
на получателе и не гарантируете защиту, но вам не гарантируется, что в любом случае вы предоставляете функцию set, которая изменяет член.
Ответ 3
Во-вторых, очень плохо, поскольку он отказывается от инкапсуляции: вы можете просто сделать соответствующее поле открытым, любой может получить к нему доступ, не зная об этом объекте. Вы не можете выполнять проверки диапазона или обновления статуса на основе изменяемых данных и т.д.
Ответ 4
Второй вариант будет довольно плохим. Причиной создания сеттеров является возможность контролировать, как пользовательская переменная изменяется. Если вы просто дадите пользователю ссылку на своего участника, вы потеряете весь контроль.
Итак, вы в значительной степени оставили первый метод. Ниже приведены два варианта, которые могут вам понравиться:
// First Variation
// ---------------
// In this one both the setter and the getter have the same name
// (which is the same as the member they control). To get a
// variable you do `int i = foo.var()` and to set it you do
// `foo.var(6)`.
class Some
{
public:
int var() const {
return var_;
}
void var(int v) {
var_ = v;
}
private:
int var_;
};
// Second Variation
// ----------------
// You can also make the setter return a reference to `this`.
// This allows you to chain setters, which can _sometimes_ be
// more readable but it also has a few disadvantages.
class Employee
{
public:
Employee& salary(double dollars) {
salary_ = dollars;
return *this;
}
Employee& name(const string& n) {
name_ = n;
return *this;
}
private:
double salary_;
std::string name_;
};
// You can now do this...
Employee emp;
emp.name("John Barlick").salary(500.00);
// ... But this can become quite ugly if you chain a large amount
// of setters (you'd then probably have to break the lines in
// order to keep the code readable). It also is (technically)
// less efficient.
// In case you have lots of setters you could probably do this:
// emp.name("John Barlick")
// .salary(500.00)
// .some(787);
// .another('g');
Ответ 5
Обычно определяются геттеры/сеттеры:
const my_class& get_data() const
{
return m_data;
}
void set_data(const my_class& _data)
{
m_data = _data;
}
Ответ 6
Прежде всего, я думаю, что это не очень эффективно
void set_data(my_class value)
{
m_data = value;
}
Вероятно, вы должны пройти по ссылке
void set_data(const my_class& value)
{
m_data = value;
}
Что касается того, какой метод вы должны выбрать, подумайте так: во втором методе вы возвращаете ссылку на свой внутренний объект, и пользователь абсолютно вправе что-либо с этим делать. С помощью первого метода вы можете контролировать то, что пользователь может или не может сделать.
Ответ 7
В то время как стандартные getters и settters, подобные методу 1, могут обеспечивать "инкапсуляцию", если эти функции не включены в заголовок, они добавляют много накладных расходов. Например, в узком цикле, даже если вы использовали ссылки, а не pass-by-value (для чего требуется дорогостоящая операция копирования памяти), постоянно приходится добавлять около восьми инструкций в x86 для каждого вызова геттера/сеттера в порядке настроить свою активационную запись в стеке, а также пролог и эпилог функции - использовать ценное время процессора и действительно повредить производительность. Поскольку вы получаете, а сеттеры не делают много, вам действительно не нужны.
Способ 2 - это то, что делает несколько контейнеров STL, например std::vector
с operator[]
, где вы перегружаете одну и ту же функцию, но определяете одно для постоянных операций, а другое для непостоянных операций... но опять же, вы добавляете ненужные накладные расходы, когда можете просто публично обращаться к члену данных (т.е. это не так, как вы являетесь базовыми указателями и другими элементами данных, управляемыми памятью, от нас, как контейнер STL). Если функция, с которой вы передаете ее, требует постоянной ссылки, она не собирается изменять член в любом случае, поэтому нет необходимости создавать такой интерфейс, если вы не пытаетесь создать общий интерфейс для доступа к элементу через хост классов. И если вы это делаете, вы должны изучить чистый виртуальный базовый класс для определения общего интерфейса.
Ответ 8
IMHO второй метод выглядит очень неудобно.