【问题标题】:Programming style: object with passages by references or value? (c++)编程风格:通过引用或值的段落的对象? (c++)
【发布时间】:2011-07-14 14:06:17
【问题描述】:

这是一个关于编程风格的一般问题。假设我有一个对象Line,它有一些方法和私有变量Point point_a_Point point_b_。假设在某些时候我需要改变两点的位置。在以下情况下,您更喜欢哪种编程风格?他们都做同样的事情(或者应该做:我没有编译,但看起来很简单)。

案例 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] 在我的软件中速度是最重要的!

【问题讨论】:

  • 公共成员变量怎么样?
  • 谢谢马蒂尼奥。我想避免公共变量。这只是我更复杂的软件的一个简化示例,我总是需要检查输入,所以我更喜欢setget 方法而不是使用公共变量。
  • 好的,请确保您考虑到了这一点。
  • 这太抽象了。改变一条线是什么意思?它仍然是同一行还是新行?是画在什么地方吗?连接到任何东西?
  • 谢谢@Bo。这只是我在软件中非常复杂的对象的一个​​简化示例。当我更改它时,我的问题中的实际对象是相同的。假设我可以使用Person 作为示例,而变化的变量是age

标签: c++ coding-style pass-by-reference pass-by-value


【解决方案1】:

在这个简单的场景中,我可能只使用公共成员变量。

否则我会提供返回 const 引用 的 getter 和匹配的 setter。

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:返回引用不是一个好主意。它不允许您更换内部实现。
  • 这是一个 const 参考!如果您要更改内部实现,那么您很可能也会更新标头。由于引用是 const,您可以确定没有其他代码修改返回的对象。
  • 完全不同意——查看任何编写良好的 C++ 库(如 boost)。只要返回副本可能“昂贵”,就会返回常量引用。如果没有(我)为 Point 提供一个实现,你就不能假设它是不正确的
  • 如果您愿意在内部实现发生变化时更改 API,那么是的,我同意您的看法。此外,谷歌的“复制省略”。
  • @Chad:setter 和 getter 的常用参数是它允许您封装内部表示。因此,在以后的版本中,您可能根本没有Point 成员(例如,您可能会决定稍后维护p1directionlength),但是当您调用get 时会动态生成它们。显然,如果您要返回一个引用,这是不可能的(或者至少,这是非常棘手的)。
【解决方案2】:

案例三完全被淘汰了,因为它完全违反了封装原则。案例二也有,只是程度稍小一些。我更喜欢选项一,但您是否考虑过可能使这些点不可变并在它更改时强制您创建一个新对象?

如果我多年前没记错的话,也是迂腐的,从技术上讲,一条线在两个方向上无限延伸。你实际上是在代表一条线segment

【讨论】:

  • 谢谢@Mark。它实际上一个段,但你真的迂腐;)这是我的问题的简化版本,我在对象中存储了许多其他临时变量,所以我不想每次都创建一个新对象。
  • 抱歉@Mark 再次提出这个问题。我又遇到了类似的问题,发现 Case 1 实际上是 en.wikipedia.org/wiki/Dependency_injection> 的一个case。你强烈反对它并支持封装吗?如果是,为什么?
【解决方案3】:

情况 2 并不比公共成员变量好。特别是,它不封装任何东西。

案例 3 使积分的所有权不明确。考虑如果您的点是调用函数中的局部变量,然后它们超出范围会发生什么。与公共成员变量相比,它也没有任何好处。

所以在这三个选项中,案例 1 是最干净的,IMO。其他选项是:

  • 只需使用公共成员变量。
  • 使Line 不可变。
  • 使用setget 函数。

【讨论】:

  • 谢谢@Oli。我不想使用案例 1 或 2 的主要原因是速度,这是我的软件中最重要的变量。在案例 1 中,我需要在构造函数中以及在更新时进行复制。在案例 2 中,我只需要在构造函数中进行复制。你怎么看?
  • -1 在这种情况下按值传递会产生多余的额外副本。
  • @Chad:请谷歌搜索“复制省略”。无论如何,OP 要求“最干净”,不一定是“最有效”。
  • 据我了解,在这种特殊情况下,复制省略在(大多数当前)符合标准的编译器上无效。在 C++0x 中(当它得到更广泛的支持时),在这种情况下复制省略成为一种有效的可能性。
【解决方案4】:

我会选择案例 1 或不可变的 Line 类。

案例 2 允许在不知道其包含线的情况下更改 Point 对象。在某些时候,您可能需要这条线知道它的点是否已更改。

案例 3 要么使 Line 对象依赖于 Points 的生命周期,要么使 Line 成为 Points 的所有者,这在 API 中并不清楚。

不可变的 Line 将允许您使用新点创建新 Line 对象。

【讨论】:

    【解决方案5】:

    案例 4 -- 更喜欢通过 const 引用而不是值(或指针)传递:

    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_
    };
    

    这更好,因为它强制封装 - 在构造后更改类中保存的变量的唯一方法是特定的 mutator 方法。

    访问器返回一个 const 引用,因此它们不能被修改(可以从它们复制)。

    整个类都是 const-correct。

    在这种情况下,引用可以说比指针更好,因为它们被保证(除非您特别打破该保证)不为 NULL。

    【讨论】:

    • -1:返回引用不是一个好主意。它不允许您更换内部实现。
    • 完全不同意——查看任何编写良好的 C++ 库(如 boost)。只要返回副本可能“昂贵”,就会返回常量引用。如果没有(我)提供 Point 的实现,你就不能假设它是不正确的。
    【解决方案6】:

    考虑另一种选择:

    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";
    }
    

    我按照上面的建议使用了名称 Segment。这种风格更接近函数式编程风格。 Segment 是不可变的,除非您使用常见的赋值语法显式重置它。 Distance 不是成员函数,因为它可以通过 Segment 的公共接口来实现。

    问候, &rzej

    【讨论】:

      猜你喜欢
      • 2018-04-03
      • 2014-02-08
      • 1970-01-01
      • 2015-02-24
      • 2011-09-27
      相关资源
      最近更新 更多