Хорошо ли, чтобы все функции установки возвращали ссылку на объект в c ++? - PullRequest
11 голосов
/ 15 апреля 2009

Хорошо ли, чтобы все функции установки возвращали ссылку на объект в c ++?

Ответы [ 8 ]

19 голосов
/ 15 апреля 2009

Это достаточно удобный шаблон, если на объект нужно установить множество вещей.

 class Foo
 {
      int x, y, z;
 public:
      Foo &SetX(int x_) { x = x_;  return *this; }
      Foo &SetY(int y_) { y = y_;  return *this; }
      Foo &SetZ(int z_) { z = z_;  return *this; }
 };

 int main()
 {
      Foo foo;
      foo.SetX(1).SetY(2).SetZ(3);
 }

Этот шаблон заменяет конструктор, который принимает три целых числа:

 int main()
 {
      Foo foo(1, 2, 3); // Less self-explanatory than the above version.
 }

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

Для справки, более полный пример такого рода техники упоминается как " Имена именованных параметров " в C ++ FAQ Lite.

Конечно, если вы используете это для именованных параметров, вы можете взглянуть на boost :: parameter . Или вы не могли бы ...

10 голосов
/ 15 апреля 2009

Вы можете вернуть ссылку на this, если хотите объединить вызовы функций-установщиков следующим образом:

obj.SetCount(10).SetName("Bob").SetColor(0x223344).SetWidth(35);

Лично я считаю, что код труднее читать, чем альтернативу:

obj.SetCount(10);
obj.SetName("Bob");
obj.SetColor(0x223344);
obj.SetWidth(35);
3 голосов
/ 15 апреля 2009

Типичное назначение этого стиля - использование для строительства объектов.

Person* pPerson = &(new Person())->setAge(34).setId(55).setName("Jack");

вместо

Person* pPerson = new Person( 34, 55, "Jack" );

Используя второй более традиционный стиль, можно забыть, было ли первое значение, переданное конструктору, возрастом или идентификатором? Это также может привести к множеству конструкторов, основанных на достоверности некоторых свойств.

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

По мере развития кода мне очень нравится тот факт, что я могу использовать компилятор, чтобы помочь мне найти все места, где создается объект, при изменении сигнатуры конструктора. Поэтому по этой причине я предпочитаю использовать обычные конструкторы C ++ вместо этого стиля.

Этот шаблон может хорошо работать в приложениях, которые со временем поддерживают свою модель данных в соответствии с правилами, аналогичными тем, которые используются во многих приложениях баз данных:

  • Вы можете добавить поле / атрибут в таблицу / класс, который по умолчанию равен NULL. (Таким образом, для обновления существующих данных требуется просто новый столбец NULL в базе данных.)
  • Код, который не является изменениями, должен по-прежнему работать так же с добавленным полем NULL.
2 голосов
/ 15 апреля 2009

Установщики IMO - это запах кода, который обычно указывает на одну из двух вещей:

Изготовление горца из крота

Если у вас есть такой класс:

class Gizmo
{
public:
  void setA(int a) { a_ = a; }
  int getA() const { return a_; }

  void setB(const std::string & b) { v_ = b; }
  std::string getB() const { return b_; }
private:
  std::string b_;
  int a_;
};

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

class Gizmo
{
public:
  std::string b_;
  int a_;
};

... Гораздо проще и, если данные настолько просты , вы ничего не потеряете.

Другая возможность, что вы могли бы быть

Изготовление крота из горы

Во многих случаях данные не так просты: возможно, вам нужно изменить несколько значений, выполнить некоторые вычисления, уведомить какой-либо другой объект; кто что знает Но если данные достаточно нетривиальны, так что вам действительно нужны сеттеры и геттеры, то нетривиально достаточно и обработки ошибок. Поэтому в этих случаях ваши геттеры и сеттеры должны возвращать какой-то код ошибки или делать что-то еще, чтобы указать, что случилось что-то плохое.

Если вы объединяете вызовы вместе, как это:

A.doA().doB().doC();

... и doA () не работает, вы действительно хотите вызывать doB () и doC ()? Я сомневаюсь в этом.

2 голосов
/ 15 апреля 2009

Этот метод используется в идиоме Именованный параметр .

2 голосов
/ 15 апреля 2009

Если ваша мотивация связана с цепочкой (например, предложение Брайана Энсинка), я бы предложил два комментария:

1. Если вы обнаружите, что часто настраиваете много вещей одновременно, это может означать, что вы должны создать struct или class, который содержит все эти настройки, чтобы они могли быть переданы одновременно Следующим шагом может быть использование этого struct или class в самом объекте ... но поскольку вы используете методы получения и установки, решение о том, как его представить внутри, будет прозрачным для пользователей класса, так что это решение будет больше касаться того, насколько сложен класс, чем что-либо.

2. Одной из альтернатив сеттеру является создание нового объекта, его изменение и возврат. Это неэффективно и неуместно в большинстве типов, особенно в изменчивых. Однако этот вариант иногда забывают, несмотря на то, что он используется в строковом классе многих языков.

2 голосов
/ 15 апреля 2009

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

вид

a.SetValues(object)(2)(3)(5)("Hello")(1.4);

Я использовал это однажды давным-давно для построения построителя выражений SQL, который обрабатывает все проблемы Escape и другие вещи.

SqlBuilder builder;

builder.select( column1 )( column2 )( column3 ).
    where( "=" )( column1, value1 )
                ( column2, value2 ).
    where( ">" )( column3, 100 ).
    from( table1 )( "table2" )( "table3" );

Я не смог воспроизвести источники за 10 минут. Так что реализация за кулисами.

0 голосов
/ 15 апреля 2009

Я бы так не думал. Как правило, вы думаете, что объект «сеттер» делает именно это.

Кроме того, если вы просто установите объект, разве у вас нет указателя на него?

...