我想深入了解何时应该使用引用或指针。
让我们以Polygon类为例,该类使用Rectangle类作为其内部边界框。
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;
}
矩形.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;
};
矩形.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();
}
点.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();
}
这段代码带来了很多问题。
- 这不会编译,因为很明显,每当我们尝试创建多边形时,它最终都会尝试创建一个不存在默认构造函数的矩形
- 我不能使用初始值设定项列表,因为很明显,边界框依赖于我的点列表中的一些计算值
- 我可以创建一个默认的构造函数,默认情况下为矩形创建两个点(0,0),但这没有多大意义
- 我可以使用指针,但我觉得这不是最好的解决方案,因为我倾向于认为这主要用于C++中的多态性,我们应该尽可能喜欢引用
那么我该如何继续?
我觉得我错过了一些关于C++的东西,可以从中学到很多。
我认为您的主要问题是如何处理需要初始化std::list<Point> _points;
和Rectangle _boundingBox;
的问题,同时还要对_points
进行一些验证。
最简单的解决方案是只给Rectangle
一个默认构造函数(或者传递两个默认点作为初始值设定项)。然后,一旦在构造函数中验证了points
参数,就可以根据这些点计算Rectangle
。
一个稍微复杂一点的替代方案是允许从ctor初始值设定项列表调用验证函数,例如:
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
}
请注意,Polygon构造函数中的循环是不必要的复杂。您可以只写_points = points;
,然后擦除额外的点(对于列表,它是O(1))。
请注意,我已经传递了值,然后使用了std::move
。原因是,如果给定的参数是一个右值,那么它可以直接移动到存储位置;而对于const &
版本,存储一个副本,然后销毁原始副本。
我会比你少用很多const &
。小型对象,如Point
和Rectangle
,不会因传递值而受到性能损失(这样可能更高效)。如前一段所述;如果您的函数接受一个参数,并且它将获取该参数的副本,那么最好通过值传递。
只有在使用而不是存储传递的值时,通过引用传递才是最好的。例如,calculateBoundingBox
。
最后,一旦您完成了这项工作,您可能需要考虑让Polygon构造函数接受点范围的迭代器对和/或std::initializer_list
。
我会将Rectangle
类的默认构造函数定义为private,并使Polygon
类成为Rectangle
类的朋友:
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
的默认构造函数,同时我会有一个在缓存性能方面更高效的具体对象。
我觉得应该有一个名为BoundingBox的单独类1) 在其构造函数中获取点的集合2) 继承自矩形
同时,Rectangle应该有一个状态,沿着NOT_a_Rectangle的行,否则它可能会引发异常。只要确保在从构造函数抛出异常时进行清理即可。
然后,您将构造边界框作为多边形构造的一部分,并且您可以验证边界框是否可以作为错误检查的一部分。(可能不是三面检查,但我不是几何专家)
BoundingBox将保留为多边形的成员。
这将是更多的RTTI。
但我突然想到,如果平移或旋转多边形,也必须平移或旋转边界框。您可能需要考虑将点列表作为自己的对象并共享它们。这将是一个更高级的话题。现在,您只需重新计算对多边形执行的操作的边界框即可。
至于是使用引用、指针还是传递值,我不知道有一个黑白列表需要考虑,但有几个是:
这个物体大到可以担心吗?一个矩形是4个浮动?
是否有接口或基类需要强制转换,而不是总是使用类本身?如果是这样的话,你别无选择,只能使用某种指针。根据情况,指针可以是唯一的、共享的、弱的等等。你必须问问自己是谁拥有它,生命的时间是什么,有循环参考吗?
大多数人可能会尽可能使用引用而不是指针,但只有在传递值不合格时才使用。
IMO,因为你只是"GetBoundingBox",我认为只按值返回一个边界框的副本,而不是一些常量引用,当然也不仅仅是一个指针,会更简单,更容易维护。
一种解决方案是为Rectangle编写一个以const std::list<Point>&
为参数的程序构造函数。它可以遍历列表一次,计算最大值和最小值x和y。然后,您的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
的东西。