Являются ли функции get и set популярными у программистов на С++?

Я из мира С# изначально, и я изучаю С++. Мне интересно, как получить и установить функции в С++. В С# их использование довольно популярно, и такие инструменты, как Visual Studio, способствуют использованию, делая их очень легкими и быстрыми в реализации. Однако это не похоже на мир С++.

Здесь код С# 2.0:

public class Foo
{
    private string bar;

    public string Bar
    {
        get { return bar; }
        set { bar = value; }
    }
}

Или, в С# 3.0:

public class Foo { get; set; }

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

Теперь, потому что я так привык к этому, я чувствую, что должен перекладывать эту привычку на свой код на С++, но действительно ли это необходимо? Я не вижу его так часто, как с С#.

В любом случае, здесь С++ из того, что я собираю:

class Foo
{
public:
    std::string GetBar() const; // Thanks for the tip Earwicker.
    void SetBar(std::string bar);
private:
    std::string bar;
}

const std::string Foo::GetBar()
{
    return bar;
}

void Foo::SetBar(std::string bar)
{
    // Also, I always wonder if using 'this->' is good practice.
    this->bar = bar;
}

Теперь мне кажется, что много работы на ногах; учитывая использование инструментов Visual Studio, реализация С# потребует буквально секунд для реализации, а С++ взял меня намного дольше, чтобы напечатать - я считаю, что это не стоит усилий, особенно когда альтернатива длится 5 строк:

class Foo
{
public:
    std::string Bar;
}

Из того, что я собираю, это преимущества:

  • Вы можете изменить детали реализации для функций get и set, поэтому вместо возвращения частного поля вы можете вернуть что-то более интересное.
  • Вы можете удалить get/set позже и сделать его только для чтения/записи (но для открытого интерфейса, кажется, это не хорошо).

И недостатки:

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

Ответ:

Почему я выбрал ответ с меньшим количеством голосов? Я был очень близок к выбору veefu answer; однако мое личное мнение (которое, по-видимому, противоречиво), заключается в том, что ответ на вопрос о поджаривании пудинга.

Ответ, который я выбрал, с другой стороны, кажется, спорит с обеих сторон; Я думаю, что геттеры и сеттеры являются злыми, если они используются чрезмерно (под этим я имею в виду, когда это не нужно и нарушало бизнес-модель), но почему бы нам не воспользоваться функцией GetBalance()?

Конечно, это было бы гораздо более универсальным, чем PrintBalance(); что, если я хочу показать это пользователю по-другому, чем того, что хотел меня класс? Теперь, в некотором смысле GetBalance() может быть недостаточно релевантным, чтобы утверждать, что "геттеры и сеттеры хороши", потому что он (или, возможно, не должен) иметь сопутствующий сеттер, и, говоря о котором, функция, называемая SetBalance(float f) может быть плохим (на мой взгляд), потому что это будет означать, что разработчик функции должен манипулировать учетной записью со стороны класса, что не очень хорошо.

Ответ 1

Я бы сказал, что предоставление аксессуаров более важно в С++, чем в С#.

С++ не имеет встроенной поддержки свойств. В С# вы можете изменить публичное поле на свойство в основном без изменения кода пользователя. В С++ это сложнее.

Для меньшего набора текста вы можете реализовать тривиальные сеттеры/геттеры как встроенные методы:

class Foo
{
public:
    const std::string& bar() const { return _bar; } 
    void bar(const std::string& bar) { _bar = bar; } 
private:
    std::string _bar;
};

И не забывайте, что геттеры и сеттеры несколько злы.

Ответ 2

Рискуя быть аргументированным, я вернусь к противоположной точке зрения, с которой я впервые столкнулся при чтении "Holub on Patterns". Это была точка зрения, которая была очень сложной, но имела смысл для меня при размышлении:

Геттеры и сеттеры злы

Использование геттеров и сеттеров противоречит основам объектно-ориентированного проектирования: абстракция данных и инкапсуляция. Чрезмерное использование геттеров и сеттеров сделает ваш код менее проворным и поддерживаемым в долгосрочной перспективе. Они в конечном счете раскрывают базовую реализацию вашего класса, блокируя детали реализации в интерфейсе класса.

Представьте, что поле 'std::string Foo:: bar' должно измениться с std::string на другой класс строк, который, скажем, лучше оптимизирован или поддерживает другой набор символов. Вам нужно будет изменить поле личных данных, получателя, сеттера и весь клиентский код этого класса, который вызывает эти геттеры и сеттеры.

Вместо того, чтобы создавать классы для "предоставления данных" и "получения данных", создайте их для "выполнения операций" или "предоставления услуг". Спросите себя, почему вы пишете функцию "GetBar". Что вы делаете с этими данными? Возможно, вы показываете эти данные или обрабатываете их. Является ли этот процесс лучше открытым как метод Foo?

Это не означает, что геттеры и сеттеры не имеют своей цели. В С# я считаю, что основной причиной их использования является взаимодействие с IDE Visual Studio GUI-design, но если вы написали их на С++, лучше всего сделать шаг назад, посмотреть на ваш дизайн и посмотреть, отсутствует.

Я попробую сделать макет для иллюстрации.

// A class that represents a user bank account
class Account {
  private:
    int balance_; // in cents, lets say 
  public:
    const int& GetBalance() { return balance_; }
    void SetBalance(int b) { balance_ = b; }
};

class Deposit {
  private:
    int ammount_;
  public:
    const int& GetAmount() { return ammount_; }
    void SetAmmount(int a) { _balance = a; }
};

void DoStuffWithAccount () {
  Account a;
  // print account balance
  int balance = a.GetBalance();
  std::cout << balance;

  // deposit some money into account
  Deposit d(10000);
  a.SetBalance( a.GetBalance() + d.GetValue());
}

Не слишком долго видеть, что это очень плохо спроектировано.

  • Целые числа - это ужасный тип данных в валюте.
  • Депозит должен быть функцией учетной записи

У геттеров и сеттеров сложнее устранить проблемы, так как клиентский код DoStuffWithAccount теперь привязан к типу данных, который использовался для реализации баланса аккаунта.

Итак, давайте пропустим этот код и посмотрим, что мы можем улучшить

// A class that represents a user bank account
class Account {
  private:
    float balance_;
  public:
    void Deposit(float b) { balance_ += b; }
    void Withdraw(float w) { balance_ -= w; }
    void DisplayDeposit(std::ostream &o) { o << balance_; }
};

void DoStuffWithAccount () {
  Account a;
  // print account balance
  a.DisplayBalance(std::cout);

  // deposit some money into account
  float depositAmt = 1000.00;
  a.Deposit(depositAmt);
  a.DisplayBalance(std::cout);
}

"Поплавок" - это шаг в правильном направлении. Конечно, вы могли бы изменить внутренний тип на "float" и по-прежнему поддерживать идиому getter/setter:

class Account {
  private:
    // int balance_; // old implementation
    float balance_; 
  public:
    // support the old interface
    const int& GetBalance() { return (int) balance_; }
    void SetBalance(int b) { balance_ = b; }
    // provide a new interface for the float type
    const float& GetBalance() { return balance_; } // not legal! how to expose getter for float as well as int??
    void SetBalance(float b) { balance_ = b; }
};

но не требуется много времени, чтобы понять, что устройство getter/setter удваивает вашу рабочую нагрузку и усложняет ситуацию, поскольку вам необходимо поддерживать как код, использующий ints, так и новый код, который будет использовать float. Функция "Депозит" упрощает расширение диапазона типов для размещения.

Учетный класс, вероятно, не лучший пример, так как "получение" баланса учетной записи является естественной операцией для учетной записи. Главное, однако, в том, что вы должны быть осторожны с геттерами и сеттерами. Не входите в привычку писать геттеры и сеттеры для каждого члена данных. Если вы не будете осторожны, достаточно легко выставить и заблокировать себя в реализации.

Ответ 3

В вашем примере:

class Foo
{
public:
    const std::string GetBar(); // Should this be const, not sure?

Вероятно, вы имеете в виду это:

std::string GetBar() const;

Помещение const в конец означает: "Эта функция не изменяет экземпляр Foo, на который он вызывается", поэтому он обозначает его как чистый получатель.

В С++ часто встречаются чистые геттеры. Примером в std::ostringstream является функция str(). Стандартная библиотека часто следует примеру повторного использования одного и того же имени функции для пары функций getter/setter - str.

В отношении того, слишком ли много работать, чтобы печатать, и стоит ли это - это кажется странным вопросом! Если вам нужно предоставить клиентам доступ к некоторой информации, укажите получателя. Если вы этого не сделаете, не делайте этого.

Ответ 4

[edit] Кажется, мне нужно подчеркнуть, что сеттерам нужно проверять параметры и применять инварианты, поэтому они обычно не так просты, как они есть здесь. [/Править]


Не со всеми, потому что для дополнительной набрав. Я часто использую их гораздо чаще, когда Visual Assist дает мне "инкапсулировать поле".

Работа с ногами не больше, если вы реализуете только стандартные сеттеры /getters inline в объявлении класса (что я обычно делаю - более сложные сеттеры перемещаются в тело).

Некоторые примечания:

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

std::string const & GetBar() const { return bar; } 

Цепочка сеттера:. Многие разработчики любят модифицировать сеттер как таковой:

Foo & SetBar(std::string const & bar) { this->bar = bar; return *this; }

Позволяет вызывать несколько наборов как таковых:

Foo foo;
foo.SetBar("Hello").SetBaz("world!");

Это не универсально принято как хорошая вещь.

__ declspec (свойство): Visual С++ предоставляет это нестандартное расширение, чтобы вызывающие могли использовать синтаксис свойств еще раз. Это немного улучшает работу в классе, но делает код вызывающего абонента более дружелюбным.


Итак, в заключение, есть немного больше проблем с работой, но несколько решений, которые нужно сделать на С++. Типичный;)

Ответ 5

На этом не существует строгого соглашения, как в С# или Java. Многие программисты на C++ просто превратили бы эту переменную в общественность, чтобы спасти себя.

Как говорили другие ответы, вам не нужно часто устанавливать и в какой-то мере получать методы.

Но если и когда вы их сделаете, вам не нужно вводить больше, чем необходимо:

class Foo
{
public:
    std::string Bar() const { return bar; }
    void Bar(const std::string& bar) { this->bar = bar; }
private:
    std::string bar;
};

Объявление функций inline в классе сохраняет типизацию и намекает компилятору, что вы хотите, чтобы функции были встроены. И это не намного больше, чем эквиваленты С#. Следует отметить, что я удалил префиксы get/set. Вместо этого у нас просто две перегрузки Bar(). Это довольно распространено в С++ (в конце концов, если он не принимает никаких аргументов, мы знаем это getter, и если он принимает аргумент, это сеттер. Нам не нужно имя, чтобы сказать нам это), и это сохраняет немного больше ввода.

Ответ 6

Я почти никогда не использовал геттеры и сеттеры в своем собственном коде. Ответ Veefu выглядит хорошо для меня.

Если вы настаиваете на том, чтобы иметь геттеры и/или сеттеры, вы можете использовать макросы, чтобы вырезать котельную.

#define GETTER(T,member) const T& Get##member() const { return member; }
#define SETTER(T,member) void Set##member(const T & value) { member = value; }

class Foo
{
public:
    GETTER(std::string, bar)
    SETTER(std::string, bar)
private:
    std::string bar;
}

Ответ 7

Получение и настройка данных членами qua data: Плохо.
Получение и установка элементов абстракции: Хорошо.

Ответ 8

Аргументы против Get/Set в терминах дизайна API в банковском примере находятся на месте. Не раскрывайте поля или свойства, если они позволят пользователям нарушать ваши бизнес-правила.

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

Автоматические свойства в С# очень просты в использовании, и существует множество сценариев (привязка данных, сериализация и т.д.), которые не работают с полями, но требуют свойств.

Ответ 9

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

Ответ 10

get and set - это боль, причиняемая людям, если вы должны использовать их на любом языке.

Eiffel намного лучше, если все, что отличается, - это объем информации, которую вы должны предоставить, чтобы получить ответ - функция с 0 parms такая же, как и доступ к переменной-члену, и вы можете свободно переключаться между ними.

Когда вы управляете обеими сторонами интерфейса, определение интерфейса не похоже на такую ​​большую проблему. Однако, когда вы хотите изменить детали реализации и называет перекомпиляцию клиентского кода, как это обычно бывает на С++, вы хотите как можно больше свести к минимуму это. Поскольку pImpl и get/set будет использоваться больше в публичных API, чтобы избежать такого ущерба.

Ответ 11

Если вы используете С++/CLI в качестве вашего varient из С++, тогда у него есть встроенная поддержка свойств на этом языке, поэтому вы можете использовать

property String^ Name;

Это то же самое, что и

String Name{get;set;}

в С#. Если вам нужен более точный контроль над методами get/set, вы можете использовать

property String^ Name
{
   String^ get();
   void set(String^ newName);
}

в заголовке и

String^ ClassName::Name::get()
{
   return m_name;
}

void ClassName::Name::set(String^ newName)
{
   m_name = newName;
}

в файле .cpp. Я не могу вспомнить, но я думаю, что у вас могут быть разные разрешения доступа для методов get и set (public/private и т.д.).

Колин

Ответ 12

Методы Get и Set полезны, если у вас есть ограничения в переменной. Например, во многих математических моделях существует ограничение на сохранение определенной переменной float в диапазоне [0,1]. В этом случае Get и Set (специально Set) могут сыграть приятную роль:

class Foo{
public:
    float bar() const { return _bar; } 
    void bar(const float& new_bar) { _bar = ((new_bar <= 1) && (new_bar >= 0))?new_bar:_bar; } // Keeps inside [0,1]
private:
    float _bar;     // must be in range [0,1]
};

Кроме того, некоторые свойства должны быть пересчитаны перед чтением. В таких случаях может потребоваться много ненужного вычислительного времени, чтобы пересчитать каждую цифру. Таким образом, способ его оптимизации состоит в пересчете только при чтении. Для этого перегрузите метод Get, чтобы обновить переменную перед ее чтением.

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

Ответ 13

Да, get и set популярны в мире С++.

Ответ 14

Компилятор будет испускать set_ и get_, если вы определяете свойство, поэтому он действительно просто сохраняет некоторую типизацию.

Это было интересное обсуждение. Это что-то из моей любимой книги "CLR через С#" .

Вот что я процитировал.

Лично мне не нравятся свойства и я хочу, чтобы они не были поддерживается в Microsoftm.NET Структура и ее программирование языки. Причина в том, что свойства выглядят как поля, но они являются методами. Это было известно вызывают феноменальное количество confu-Сьон. Когда программист видит код, который, как представляется, обращается к поле, есть много предположений что программист делает это не верны для свойства. Для Например,

  • Свойство может быть доступно только для чтения или только для записи; доступ к полю всегда есть читаемый и доступный для записи. Если вы определяете недвижимость, лучше всего предложить как получить и установить методы доступа.
  • Метод свойства может вызывать исключение; доступ к полю никогда не запускается исключение.

  • Свойство не может быть передано как параметр out или ref для Способ; поле может.

  • Метод свойства может занять много времени; доступ к полям всегда
    завершается непосредственно. Общее причина использования свойств - это выполнить синхронизацию потоков, которая может остановить поток навсегда, и
    поэтому собственность не должна быть используется, если синхронизация потоков - обязательный. В этой ситуации метод является предпочтительным. Кроме того, если ваш класс может получить доступ удаленно (например,
    ваш класс получен из System.MashalByRefObject), вызывающий метод свойств будет очень медленный, и, следовательно, метод предпочтение отдается собственности. В моем мнения, классы, полученные из
    MarshalByRefObject никогда не должен использовать свойства.

  • Если вы вызываете несколько раз подряд, метод свойства может возвращать другое значение каждый раз;
    поле возвращает одно и то же значение время. Класс System.DateTime имеет только для чтения Теперь свойство, которое возвращает значение текущую дату и время. Каждый раз вы запрашиваете это свойство, оно будет вернуть другое значение. Это
    ошибка, и Microsoft хочет, чтобы они могут исправить класс, сделав Теперь метод вместо свойства.

  • Метод свойств может вызывать наблюдаемые побочные эффекты; доступ к полям никогда не делает. Другими словами, пользователь тип должен иметь возможность устанавливать различные свойства, определенные типом в любом порядок, который он или она выбирает без замечая какое-либо другое поведение в
    тип.

  • Метод свойства может потребовать дополнительную память или вернуть ссылка на то, что не является фактически часть состояния объекта, поэтому для изменения возвращаемого объекта есть не влияет на исходный объект;
    запрос поля всегда возвращает значение ссылка на объект, который является гарантированно будет частью оригинала состояние объекта. Работа с
    свойство, которое возвращает копию, может быть очень запутанным для разработчиков, и
    эта характеристика часто отсутствует документированы.