Стиль программирования: объект с переходами по ссылкам или по значению? (C ++) - PullRequest
0 голосов
/ 14 июля 2011

Это общий вопрос о стиле программирования.Допустим, у меня есть объект Line, который имеет некоторые методы и частные переменные Point point_a_ и Point point_b_.Допустим, в какой-то момент мне нужно изменить положение двух точек.Какой стиль программирования вы бы предпочли между следующими случаями?Все они делают одно и то же (или должен сделать: я не скомпилировал, но кажется довольно простым).

CASE 1

Class Line {
public:
  Line(Point point_a, Point point_b) : point_a_(point_a), point_b_(point_b) {}

  void UpdatePoints(Point point_a, Point point_b) { 
    point_a_ = point_a; point_b_ = point_b;
  }

  double Distance();

private:
  Point point_a_;
  Point point_b_;
};

int main (int argc, char * const argv[]) {
  Point point_a(0,0,0);
  Point point_b(1,1,1);
  Line line(point_a,point_b);
  std::cout<<line.Distance()<<"\n";

  point_a.x = 1;
  line.UpdatePoints(point_a,point_b);
  std::cout<<line.Distance()<<"\n";
}

СЛУЧАЙ 2

Class Line {
public:
  Line(Point point_a, Point point_b) : point_a_(point_a), point_b_(point_b) {}
  Point& point_a() { return point_a_; }
  Point& point_b() { return point_b_; }
  double Distance();

private:
  Point point_a_;
  Point point_b_;
};

int main (int argc, char * const argv[]) {
  Point point_a(0,0,0);
  Point point_b(1,1,1);
  Line line(point_a,point_b);
  std::cout<<line.Distance()<<"\n";

  line.point_a().x = 1;
  std::cout<<line.Distance()<<"\n";
}

СЛУЧАЙ 3

Class Line {
public:
  Line(Point* point_a, Point* point_b) : point_a_(point_a), point_b_(point_b) {}

  double Distance();

private:
  Point* point_a_;
  Point* point_b_;
};

int main (int argc, char * const argv[]) {
  Point point_a(0,0,0);
  Point point_b(1,1,1);
  Line line(&point_a,&point_b);
  std::cout<<line.Distance()<<"\n";

  point_a.x = 1;
  std::cout<<line.Distance()<<"\n";
}

Любая обратная связь приветствуется !!

Спасибо!

[EDIT] Скорость имеет первостепенное значение в моем программном обеспечении!

Ответы [ 6 ]

2 голосов
/ 14 июля 2011

В этом простом сценарии я мог бы просто использовать публичные переменные-члены.

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

class Line {
public:
    Line(const Point& p1, const Point&p2) : m_p1(p1), m_p2(p2) {}

    const Point& p1() const 
    { return m_p1; }

    const Point& p2() const
    { return m_p2; }

    void setP1(const Point& p1)
    { m_p1 = p1; }

    void setP2(const Point& p2)
    { m_p2 = p2; }

private:
    Point m_p1;
    Point m_p2;
};
1 голос
/ 14 июля 2011

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

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

0 голосов
/ 14 июля 2011

Рассмотрим другой вариант:

class Segment {
public:
    Segment(Point point_a, Point point_b);
    Point point_a() const;
    Point point_b() const;

private:
   Point point_a_;
   Point point_b_;
};


double Distance( Segment seg );

int main (int argc, char * const argv[]) {
    Point point_a(0, 0, 0);
    Point point_b(1, 1 ,1);
    Segment seg(point_a, point_b);
    std::cout << Distance(seg) << "\n";

    point_a.x = 1;
    seg = Segment(point_a, point_b); // reset
    std::cout << Distance(seg) << "\n";
}

Я использовал имя Сегмент согласно предложению выше. Этот стиль ближе к стилю функционального программирования. Сегмент является неизменным, если вы явно не сбросите его с помощью общего синтаксиса присваивания. Расстояние не является функцией-членом, потому что оно может быть реализовано в терминах открытого интерфейса сегмента.

С уважением, & Rzej

0 голосов
/ 14 июля 2011

Случай 4 - предпочтение передачи по константной ссылке, а не по значению (или указателю):

class Line
{
public:
    Line(const Point& a, const Point& b) : a_(a), b_(b)
    {}

    const Point& get_a() const { return a_; }
    const Point& get_b() const { return b_; }

    void set_a(const Point& a) { a_ = a; }
    void set_b(const Point& b) { b_ = b; }

private:
    Point a_;
    Point b_
};

Это лучше в том смысле, что в принудительной инкапсуляции - единственный способ изменить переменные, хранящиеся в классе после построения, - это специальный метод мутатора.

Средства доступа возвращают константную ссылку, поэтому их нельзя изменить (копии, сделанные из них, могут быть).

Класс в целом сделан const-правильным.

Ссылки в этом случае, возможно, лучше, чем указатели, потому что они гарантированно (если вы специально не нарушаете эту гарантию) не равны NULL.

0 голосов
/ 14 июля 2011

Я бы выбрал либо случай 1, либо неизменный класс Line.

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

Случай 3 либо делает объект Line зависимым от времени жизни точек, либо делает линию владельцем точек, что не ясно из API.

Неизменная Линия позволит вам создать новый объект Line с новыми точками.

0 голосов
/ 14 июля 2011

Случай 2 не лучше открытых переменных-членов.В частности, он ничего не заключает в себе.

Случай 3 делает владение точками неясным.Подумайте, что произойдет, если ваши точки являются локальными переменными в вызывающей функции, а затем они выходят за рамки.Он также не дает никаких преимуществ перед общедоступными переменными-членами.

Таким образом, из этих трех вариантов вариант 1 является наиболее чистым, ИМО.Другие варианты:

  • просто использовать открытые переменные-члены.
  • сделать Line неизменным.
  • использовать set и get функции.
...