C++ 我应该使用指针还是引用?

C++ should I use pointer or reference?

我想深入了解何时应该使用引用或指针。

让我们以使用矩形 class 作为其内部边界框的多边形 class 为例。

Polygon.h

class Polygon {

private:
    std::list<Point> _points;
    Rectangle _boundingBox;

public:
    Polygon(const std::list<Point> &);

public:
    const std::list<Point> &getPoints() const;
    const Rectangle &getBoundingBox() const;

private:
    void setBoundingBox();

};

Polygon.cpp

#include <iostream>
#include "Polygon.h"

Polygon::Polygon(const std::list<Point> &points)
{
    if (points.size() < polygon::MIN_SIDE + 1) {
        throw std::invalid_argument("A polygon is composed of at least 3 sides.");
    }

    if (points.front() != points.back()) {
        throw std::invalid_argument("A polygon must be closed therefore the first point must be equal to the last one.");
    }

    std::list<Point>::const_iterator it;
    for (it = ++points.begin(); it != points.end(); ++it) {
        this->_points.push_back(*it);
    }
    this->setBoundingBox();
}

void Polygon::translate(const std::array<float, 2> &vector)
{
    std::list<Point>::iterator it;
    for (it = this->_points.begin(); it != this->_points.end(); ++it) {
        (*it).setX((*it).getX() + vector[0]);
        (*it).setY((*it).getY() + vector[1]);
    }
    Point topLeft = this->_boundingBox->getTopLeft();
    Point bottomRight = this->_boundingBox->getBottomRight();

    topLeft.setX(topLeft.getX() + vector[0]);
    topLeft.setY(topLeft.getY() + vector[1]);
    bottomRight.setX(bottomRight.getX() + vector[0]);
    bottomRight.setY(bottomRight.getY() + vector[1]);
}

const std::list<Point> &Polygon::getPoints() const
{
    return this->_points;
}

const Rectangle &Polygon::getBoundingBox() const
{
    return this->_boundingBox;
}

void Polygon::setBoundingBox()
{
    float xMin = this->_points.front().getX();
    float xMax = this->_points.front().getX();
    float yMin = this->_points.front().getY();
    float yMax = this->_points.front().getY();

    std::list<Point>::const_iterator it;
    for (it = this->_points.begin(); it != this->_points.end(); ++it)
    {
        Point point = *it;
        if (point.getX() < xMin) {
            xMin = point.getX();
        }
        if (point.getX() > xMax) {
            xMax = point.getX();
        }
        if (point.getY() < yMin) {
            yMin = point.getY();
        }
        if (point.getY() > yMax) {
            yMax = point.getY();
        }
    }
    this->_boundingBox = new Rectangle(Point(xMin, yMin), Point(xMax, yMax));
}

std::ostream &operator<<(std::ostream &out, const Polygon &polygon)
{
    std::list<Point>::const_iterator it;

    for (it = polygon.getPoints().begin(); it != polygon.getPoints().end(); ++it) {
        out << (*it);
        if (it != polygon.getPoints().end()) {
            out << " ";
        }
    }
    return out;
}

Rectangle.h

#pragma once

#include <stdexcept>

#include "Point.h"

class Rectangle {

private:
    Point _topLeft;
    Point _bottomRight;

public:
    Rectangle(const Point &, const Point &);

public:
    const Point &getTopLeft() const;
    const Point &getBottomRight() const;
    float getWidth() const;
    float getHeight() const;
};

Rectangle.cpp

#include "Rectangle.h"

Rectangle::Rectangle(const Point &topLeft, const Point &bottomRight)
{
    if (topLeft.getX() > bottomRight.getX() || topLeft.getY() > bottomRight.getY()) {
        throw std::invalid_argument("You must specify valid top-left/bottom-right points");
    }
    this->_topLeft = topLeft;
    this->_bottomRight = bottomRight;
}

const Point &Rectangle::getTopLeft() const
{
    return this->_topLeft;
}

const Point &Rectangle::getBottomRight() const
{
    return this->_bottomRight;
}

float Rectangle::getWidth() const
{
    return this->_bottomRight.getX() - this->_topLeft.getX();
}

float Rectangle::getHeight() const
{
    return this->_bottomRight.getY() - this->_topLeft.getY();
}

Point.h

#pragma once

#include <ostream>
#include <cmath>

class Point {

private:
    float _x;
    float _y;

public:
    Point(float = 0, float = 0);

public:
    float distance(const Point &);

public:
    float getX() const;
    float getY() const;
    void setX(float);
    void setY(float);
};

std::ostream &operator<<(std::ostream &, const Point &);
bool operator==(const Point &, const Point &);
bool operator!=(const Point &, const Point &);

Point.cpp

#include "Point.h"

Point::Point(float x, float y)
{
    this->_x = x;
    this->_y = y;
}

float Point::distance(const Point &other)
{
    return std::sqrt(std::pow(this->_x - other.getX(), 2) + std::pow(this->_y - other.getY(), 2));
}

float Point::getX() const
{
    return this->_x;
}

float Point::getY() const
{
    return this->_y;
}

void Point::setX(float x)
{
    this->_x = x;
}

void Point::setY(float y)
{
    this->_y = y;
}

std::ostream &operator<<(std::ostream &out, const Point &point)
{
    out << "(" << point.getX() << ", " << point.getY() << ")";
    return out;
}

bool operator==(const Point &p1, const Point &p2)
{
    return p1.getX() == p2.getX() && p1.getY() == p2.getY();
}

bool operator!=(const Point &p1, const Point &p2)
{
    return p1.getX() != p2.getX() || p1.getY() != p2.getY();
}

这段代码带来了很多问题。

我应该如何进行?

我觉得我遗漏了一些关于 C++ 的东西,但可以从中学到很多东西。

我觉得你应该有一个单独的 class 叫做 BoundingBox 1) 在其构造函数中获取点的集合 2) 继承自Rectangle

同时,Rectangle 应该有一个状态,与 NOT_A_RECTANGLE 类似,否则它可能会抛出异常。请确保在从构造函数中抛出异常时进行清理。

然后您将构建边界框作为多边形构建的一部分,并且您可以验证边界框是否可能作为错误检查的一部分。 (可能不是 3 边检查,但我不是几何专家)

BoundingBox 仍是多边形的成员。

这将是更多的 RTTI。

不过我突然想到,如果您平移或旋转多边形,您也会平移或旋转边界框。您可能需要考虑将点列表作为自己的对象并共享它们。这将是一个更高级的话题。您现在只需重新计算对多边形执行的操作的边界框即可。

至于是使用引用、指针还是按值传递,我不知道是否有黑名单和白名单来考虑,但有一些是:

物体大到足以担心它吗?一个矩形是4个浮点数?

是否有接口或基础 classes 需要转换为,而不是总是使用 class 本身?如果是这样,您别无选择,只能使用某种指针。根据情况,指针可以是唯一的、共享的、弱的等。你要问问自己谁拥有它,生命周期是多少,是否有循环引用?

大多数人可能会尽可能使用引用而不是指针,但只有在按值传递不合格时才使用。

IMO,因为你只是 "GetBoundingBox",我认为只按值 return 边界框的副本而不是一些 const 引用会更简单且更易于维护,而且绝对超过一个指针。

我会将 Rectangle class 的默认构造函数定义为私有的,并且我会让 Polygon class 成为 Rectangle [=23= 的朋友]:

class Rectangle {
  friend class Polygon;

  Point _topLeft;
  Point _bottomRight;

  Rectangle(); // accessible only to friends

  public:

  Rectangle(Point const&, Point const&);
  ...
};

然后在setBoundingBox():

void Polygon::setBoundingBox() {
  ...
  _boundingBox._topLeft     = Point(xMin, yMin);
  _boundingBox._bottomRight = Point(xMax, yMax);
}

因此,我不会公开 Rectangle 的默认构造函数,同时我将拥有一个在缓存性能方面更高效的具体对象。

我认为你的主要问题是关于如何处理需要同时初始化 std::list<Point> _points;Rectangle _boundingBox; 的问题,同时还要对 _points.[=27 进行一些验证=]

最简单的解决方案是只给 Rectangle 一个默认构造函数(或传递两个默认点作为初始值设定项)。然后,一旦您在构造函数中验证了 points 参数,就可以根据这些点计算 Rectangle


一个稍微复杂的替代方案是允许从构造函数初始化列表中调用验证函数,例如:

Polygon::Polygon(std::list<Point> points)
    : _points( validate_point_list(points), std::move(points) ), _boundingBox( calculateBoundingBox(_points) ) 
{
}

哪里有函数(可以是免费函数):

void validate_point_list(std::list<Point> &points)
{
    if (points.size() < polygon::MIN_SIDE + 1)
        throw std::invalid_argument("A polygon is composed of at least 3 sides.");

    if (points.front() != points.back())
        throw std::invalid_argument("A polygon must be closed therefore the first point must be equal to the last one.");

// caller must pass in same first and last point, but we only store one of the two
    points.erase( points.begin() );
}

Rectangle calculateBoundingBox(std::list<Point> const &_points)
{
    // whatever logic you have in setBoundingBox, except return the answer
}

请注意,多边形构造函数中的循环不必要地复杂。您可以只写 _points = points; 然后删除额外的点(对于列表来说是 O(1))。


注意,我是传值,然后用std::move。原因是如果给定的参数是一个右值,那么它可以直接移动到它被存储的地方;而对于 const & 版本,会存储一个副本,然后销毁原始版本。

我会用 const & 比你少很多。小对象,例如 PointRectangle,不会因按值传递而受到性能损失(甚至可能更有效)。并且如上一段所述;如果您的函数接受一个参数并且它将获取该参数的副本,则最好按值传递。

仅当您使用但不存储传递的值时,通过引用传递才是最好的。例如,calculateBoundingBox.


最后,一旦你完成这项工作,你可能想要考虑让 Polygon 构造函数接受点范围的迭代器对,and/or a std::initializer_list

一个解决方案是为 Rectangle 编写一个程序化的构造函数,它的参数是 const std::list<Point>&。它可以遍历列表一次,计算最大值和最小值 xy。然后,您的 Polygon 构造函数将变为:

Polygon::Polygon(const std::list<Point> &points)
: _points(points),
: _boundingBox(points)
{
  // ...
}

另一种方法是将用于从点列表中查找边界框的代码移动到辅助函数,然后定义一个移动构造函数 Rectangle::Rectangle( Rectangle&& x )。在这种情况下,您的 Polygon 构造函数将是:

Polygon::Polygon(const std::list<Point> &points)
: _points(points),
: _boundingBox( findBoundingBox(points) )
{
  // ...
}

无论哪种方式,您都可以使用赋值更新边界框,因此您可能需要像 Rectangle& Rectangle::operator= ( Rectangle&& x ) 这样的赋值运算符来提高效率。如果 Rectangle 只是普通旧数据,您可以跳过 Rectangle&& 版本。但是如果你经常这样做,你可能会超载 Rectangle& findBoundingBox( const std::list<Point>& src, Rectangle& dest ) 以在没有复制的情况下就地更新。

顺便说一下,我不鼓励您使用以下划线开头的标识符,因为这些名称保留在 C++ 的全局命名空间中,并且您的库可能会声明名为 _point 的内容。