I would like to have an insight about whenever I should be using references or pointers.
Let's take the example of a Polygon class using a Rectangle class for its internal bounding box.
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();
}
A lot of questions come with this snippet of code.
This does not compile because obviously whenever we try to create a Polygon, it ends up trying to create a Rectangle with a default constructor which does not exist.
I can't use initializer list because obviously the bounding box depends on some computed values from my list of points.
I could create a default constructor creating two Point(0, 0) by default for the Rectangle but this does not make much sense.
I could use pointers but then I feel this is not the nicest solution as I tend to think this is mostly used for polymorphism in C++ we should prefer reference whenever possible.
How should I then proceed ?
I feel I am missing something out about C++ and could learn a lot from this.
I think your main question is about how to deal with the problem of needing to initialize both std::list<Point> _points; and Rectangle _boundingBox;, while also doing some validation of _points.
The simplest solution is to just give Rectangle a default constructor (or pass two default Points as initializer). Then once you have validated the points argument in the constructor, you calculate the Rectangle based on the points.
A slightly more complicated alternative is to allow the validation function to be invoked from the ctor-initializer list, e.g.:
Polygon::Polygon(std::list<Point> points)
: _points( validate_point_list(points), std::move(points) ), _boundingBox( calculateBoundingBox(_points) )
{
}
where you have functions (which could be free functions):
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() );
}
and
Rectangle calculateBoundingBox(std::list<Point> const &_points)
{
// whatever logic you have in setBoundingBox, except return the answer
}
Note that the loop in your Polygon constructor is unnecessarily complicated. You could have just written _points = points; and then erased the extra point (which is O(1) for lists).
Note that I have passed by value and then used std::move. The reason is that if the argument given is a rvalue then it can just be moved right on through into where it's being stored; whereas with the const & version, a copy is stored and then the original is destructed.
I would use const & a lot less than you did. Small objects , such as Point and Rectangle, don't suffer a performance penalty from pass-by-value (and might even be more efficient that way). And as mentioned in the previous paragraph; if your function accepts a parameter and it is going to take a copy of that parameter, it's better to pass by value .
Passing by reference is best only when you are using but not storing the values passed. For example, calculateBoundingBox.
Finally, once you get this working, you might want to think about having the Polygon constructor accept an iterator pair of points range, and/or a std::initializer_list.
I would defined a default constructor for Rectangle class as private and I would make Polygon class a friend of Rectangle class:
class Rectangle {
friend class Polygon;
Point _topLeft;
Point _bottomRight;
Rectangle(); // accessible only to friends
public:
Rectangle(Point const&, Point const&);
...
};
And then in setBoundingBox():
void Polygon::setBoundingBox() {
...
_boundingBox._topLeft = Point(xMin, yMin);
_boundingBox._bottomRight = Point(xMax, yMax);
}
Thus, I wouldn't expose the default constructor of Rectangle and at the same time I would have a concrete object which is more efficient in terms of cache performance.
I feel as though you should have a separate class called BoundingBox that
1) Takes a collection of points in its constructor
2) Is inherited from Rectangle
Meanwhile, Rectangle should have a state, along the lines of NOT_A_RECTANGLE or it could throw an exception. Just be sure you clean up when throwing exceptions from a constructor.
Then you would construct the bounding box as part of the construction of the polygon and you can verify that a bounding box is possible as part of your error checking. (probably rather than 3 sides check, but I am no geometry expert)
BoundingBox would remain a member of Polygon.
This would be more RTTI.
It occurs to me though, that if you translate or rotate the polygon, you've also go to translate or rotate the bounding box. You might want to consider making the list of points its own object and sharing them. This would be a more advanced topic. You can for now get away with just recalculating the bounding box on operations performed upon the polygon.
As to whether to use a reference, a pointer, or pass by value, I don't know that there is a black and white list of things to consider for this, but a few are:
Is the object large enough to even worry about it? A rectangle is 4 floats?
Are there interfaces or base classes you will need to cast to, rather than always using the class itself? If so, you've got no choice but to use a pointer of some sort. The pointer could be unique, shared, weak, etc. depending on the situation. You have to ask yourself who owns it, whats the life time, and are there circular references?
Most people will probably use a reference whenever possible rather than a pointer, but only when passing by value doesn't qualify.
IMO, since you are just "GetBoundingBox", I think it would be simple and more maintainable to just return a copy of the bounding box by value rather than some const reference and definitely more than a pointer.
One solution would be to write a programmatic constructor for Rectangle that takes as its argument a const std::list<Point>&. It could traverse the list once, computing the maximum and minimum x and y. Then, your Polygon constructor would become:
Polygon::Polygon(const std::list<Point> &points)
: _points(points),
: _boundingBox(points)
{
// ...
}
An alternative is to move the code to find the bounding box from a list of points to a helper function, then define a move constructor Rectangle::Rectangle( Rectangle&& x ). In that case, your Polygon constructor would be:
Polygon::Polygon(const std::list<Point> &points)
: _points(points),
: _boundingBox( findBoundingBox(points) )
{
// ...
}
Either way, you could update a bounding box with assignment, so you might want an assignment operator like Rectangle& Rectangle::operator= ( Rectangle&& x ) to make that more efficient. You can skip the Rectangle&& versions if a Rectangle is just Plain Old Data. But if you do this a lot, you might overload Rectangle& findBoundingBox( const std::list<Point>& src, Rectangle& dest ) to update in place with no copying.
On a minor side note, I’d discourage you from using identifiers that begin with underscores, since those names are reserved in the global namespace in C++, and your libraries might declare something named _point.
Related
I have a class ShapeDisplay that stores a set of Rectangles. I would like to store them sorted, therefore I use a std::set. My intention is to provide a custom comparator, which compares the origin (x, y) of the rectangle to a reference point (x, y) in the display.
However, in order to achieve this, the comparator needs access to m_reference. How do I use a custom comparator, that needs access to the class members? Is my design flawed? I know there are newer ways to provide the comparator as in this link, but that doesn't solve my access issue.
Alternatively, I could just have a std::vector that I keep sorted, such that each new Rectangle is inserted in the right position. But since std::set::insert() should do that automatically with a custom comparator, I would prefer that.
Thank you.
struct Point
{
int x;
int y;
};
struct Rectangle
{
int x;
int y;
int width;
int height;
};
class ShapeDisplay
{
void insertShape(Rectangle rect)
{
m_shapes.insert(rect);
}
void setReference(Point reference)
{
m_reference = reference;
}
private:
struct CenterComparator
{
bool operator() (const Rectangle & a, const Rectangle & b) const
{
double distA = std::sqrt(std::pow(a.x - m_reference.x, 2)
+ std::pow(a.y - m_reference.y, 2));
double distB = std::sqrt(std::pow(b.x - m_reference.x, 2)
+ std::pow(b.y - m_reference.y, 2));
return distA < distB;
}
};
std::set<Rectangle, CenterComparator> m_shapes;
Point m_reference;
};
CenterComparator isn't related to ShapeDisplay, it isn't aware of its members and it isn't derived from ShapeDisplay. You need to provide CenterComparator with its own reference Point. You then need to provide an instance of CenterComparator whose reference point is set.
Note that if you change that comparator's reference point in any way you will break std::set's sorting resulting in Undefined Behavior if you try to use it. So whenever setReference is called, you need to create a new set with a new comparator and copy over the old set.
Here is your code, adapted with these changes. I assumed you meant setReference and insertShape to be part of the public interface.
#include <cmath>
#include <set>
struct Point
{
int x;
int y;
};
struct Rectangle
{
int x;
int y;
int width;
int height;
};
class ShapeDisplay
{
public:
void insertShape(Rectangle rect)
{
m_shapes.insert(rect);
}
void setReference(Point reference)
{
m_reference = reference;
// Create a comparator using this new reference
auto comparator = CenterComparator{};
comparator.reference = m_reference;
// Create a new set
auto new_shapes = std::set<Rectangle, CenterComparator>(
std::begin(m_shapes), std::end(m_shapes), // Copy these shapes
comparator); // Use this comparator
m_shapes = std::move(new_shapes);
}
private:
struct CenterComparator
{
bool operator() (const Rectangle & a, const Rectangle & b) const
{
double distA = std::sqrt(std::pow(a.x - reference.x, 2)
+ std::pow(a.y - reference.y, 2));
double distB = std::sqrt(std::pow(b.x - reference.x, 2)
+ std::pow(b.y - reference.y, 2));
return distA < distB;
}
Point reference;
};
std::set<Rectangle, CenterComparator> m_shapes;
Point m_reference;
};
I have made a base class called Shape_2D, which would be inherited by all the shapes class
Shape_2D.h
private:
std::vector<Point> points_within_shape_;
std::vector<Point> points_on_perimeter_;
public:
std::vector<Point> get_Points_Within_Shape();
std::vector<Point> get_Points_On_Perimeter();
There's only 1 constructor for the Shape_2D class
Shape_2D.cpp
Shape_2D::Shape_2D(const std::string &name, const bool &contain_Warp_Space)
: name_(name), contain_warp_space_(contain_Warp_Space) {}
std::vector<Point> Shape_2D::get_Points_Within_Shape()
{
return this->points_within_shape_;
}
std::vector<Point> Shape_2D::get_Points_On_Perimeter()
{
return this->points_on_perimeter_;
}
Given the code above, I want to get all the points within a shape (Rectangle in this case) and have it return a vector for all the points.
The function for that is implemented individually for each shape
This example is the rectangle class
Rectangle.h
std::vector<Point> get_All_Point_In_Shape() override;
std::vector<Point> get_All_Point_On_Shape() override;
In the Constructor of Rectangle, I immediately tried to assign the vector using the function defined below.
Rectangle.cpp
Rectangle::Rectangle(const std::array<Point, 4> &vertices, const bool &warp_space)
: Shape_2D("Rectangle", warp_space), vertices_(vertices), area_(compute_Area())
{
get_Points_Within_Shape() = get_All_Point_In_Shape();
get_Points_On_Perimeter() = get_All_Point_On_Shape();
}
std::vector<Point> Rectangle::get_All_Point_In_Shape()
{
std::vector<Point> points_within_shape;
for (int x = vertices_[0].get_X() + 1; x < vertices_[2].get_X(); x++)
{
for (int y = vertices_[1].get_Y() + 1; y < vertices_[0].get_Y(); y++)
{
if (is_Point_In_Shape(x, y))
points_within_shape.push_back(Point(x, y));
}
}
return points_within_shape;
}
std::vector<Point> Rectangle::get_All_Point_On_Shape()
{
std::vector<Point> points_on_perimeter;
for (int x = vertices_[0].get_X(); x <= vertices_[2].get_X(); x++)
{
for (int y = vertices_[1].get_Y(); y <= vertices_[0].get_Y(); y++)
{
if (is_Point_On_Shape(x, y))
points_on_perimeter.push_back(Point(x, y));
}
}
return points_on_perimeter;
}
However, when i run them through the debugger, i find out that despite the function works properly, the vector declared in Shape_2D will not update accordingly. Is there something i can do to fix this?
Your problem is that you defined functions get_Points_Within_Shape() and get_Points_On_Perimeter() as returning copies of internal vectors. Instead you should define them to return references to internal vectors. Also, usually there are 2 versions for such functions: usual function returning usual vector and const function returning const vector. I.e. your declaration should look something like this:
std::vector<Point>& get_Points_Within_Shape() {return points_within_shape_;}
const std::vector<Point>& get_Points_Within_Shape() const {return points_within_shape_;}
The problem is that methods get_Points_Within_Shape and get_Points_On_Perimeter are returning a copy of the points vector, so you basically just assign to that copy, which is then destroyed after the statement execution is finished (because it was not saved into a variable).
Your options here would be to either make points_within_shape_ and points_on_perimeter_ protected, so the deriving class can access them (like suggested in the comments), or to provide overload for a getter function that allows modifying the data:
std::vector<Point> &get_Points_Within_Shape()
{
return points_within_shape_;
}
const std::vector<Point> &get_Points_Within_Shape() const
{
return points_within_shape_;
}
Notice that first method provides ability to access the data, since it returns a non-const reference. Another alternative would be to implement a setter in the base class:
void set_Points_Within_Shape(const std::vector<Point> &points)
{
points_within_shape_ = points;
}
Even if you decide to use a setter, you should still change your getter class to return const std::vector<Point> &. This way, you won't copy all the points into a new vector each time you want to read them. If there are many points in the vector, this can be major performance penalty.
P.S.
Although it is the simplest way, please avoid declaring the derived class a friend. This should be used only in some specific cases, and definitely not in a case where one class is inheriting from another.
I have just finished writing my first mini-project in object-orinted (cpp). It seems that the program is running well, but when I compile it I recieve the following warning:
: warning C4715: 'Collection::getCircleAt' : not all control paths return a value.
I cant understand what is the reason for this warning.
I would like to add some words about my program:
The program inputs a collection of circles in the integers xy plane, and checks which circle includes a given point in the plane. For that task, I used classes.
The class 'Point' represents a point by its coordinates.
The class 'Circle' represents a colorized circle in the plane. That class contains a variable of type "Point" which represents its center of circle, and a radius. That class also contains a variable named "color", (represented by integers 0 or 2, when 0 means that a circle does not include the point given, and 2 means the contrary), and a function that check if the given point is included in a certain circle.
Finally, we had the class 'Collection', which represents a collection of circles.
The variable "count" is the number of circles, and the variable "circles" is an array of pointers to circles in the collection. This class contains the function getCircleAt that returns the circle which includes the point. That's the function which makes the warning shown above.
Heres is my code:
#include <iostream>
using namespace std;
#include "point.h"
#include "circle.h"
#include "collection.h"
int main()
{
Collection g(4, 3, 2, 0);
cout << "-- before setColor(2) --" << endl;
g.print();
Point p(5, 1);
g.getCircleAt(p).setColor(2);
cout << "-- after setColor(2) --" << endl;
g.print();
return 0;
}
#ifndef POINT_H
#define POINT_H
class Point
{
public:
Point(int x, int y);
int getX() const;
int getY() const;
void setX(int x);
void setY(int y);
void print() const;
private:
int x, y;
};
#endif
#ifndef CIRCLE_H
#define CIRCLE_H
#include "point.h"
class Circle
{
public:
Circle(int x, int y, int r, int color);
int getColor() const;
void setColor(int color);
bool contains(const Point &p) const;
void print() const;
private:
const Point center;
int radius, color;
};
#endif
#ifndef COLLECTION_H
#define COLLECTION_H
#include "circle.h"
class Collection
{
public:
Collection(int radius, int width, int height, int color);
~Collection();
Circle& getCircleAt(const Point &p);
void print() const;
private:
int count;
Circle **circles;
};
#endif
#include <iostream>
using namespace std;
#include "point.h"
Point::Point(int x,int y )
{
setX(x);
setY(y);
}
void Point::setX(int x)
{
this->x=x;
}
void Point::setY(int y)
{
this->y=y;
}
int Point::getX() const
{
return x;
}
int Point::getY() const
{
return y;
}
void Point::print() const
{
cout <<"x="<< this->x <<" "<<"y" << this->y ;
cout <<" ";
#include <iostream>
using namespace std;
#include "circle.h"
Circle::Circle(int x=0,int y=0,int r=0,int color=0):center(x, y),radius(r),color(color)
{
}
int Circle::getColor() const
{
return color;
}
void Circle::setColor(int color)
{
this->color=color;
}
bool Circle::contains(const Point &p) const
{
int distX, distY;
distX=p.getX()-center.getX();
distY=p.getY()-center.getY();
if ((distX*distX + distY*distY) > (radius*radius))
return false;
return true;
}
void Circle::print() const
{
cout<<endl<<"the center of the circle is ("<<center.getX()<<" ,"<<center.getY()<<")"<<" radius "<<radius<<" color "<<color<<endl;
}
#include <iostream>
using namespace std;
#include "collection.h"
Collection::Collection(int radius, int width, int height, int color)
{
int i ,j;
count=height*width;
circles=new Circle* [count];
for(i=0;i<height;i++)
for(j=0;j<width;j++)
circles[j+(i*width)]=new Circle (j*2*radius,i*2*radius,radius,color);
}
Collection::~Collection()
{
delete []circles;
}
Circle& Collection::getCircleAt(const Point &p)
{
for(int i=0;i<count;i++)
if(circles[i]->contains(p) )
return *(circles)[i];
}
void Collection::print() const
{
for (int i=0;i<count;i++)
circles[i]->print();
}
The expected output is:
-- before setColor(2) --
Circle center=(0,0) radius=4 color=0
Circle center=(8,0) radius=4 color=0
Circle center=(16,0) radius=4 color=0
Circle center=(0,8) radius=4 color=0
Circle center=(8,8) radius=4 color=0
Circle center=(16,8) radius=4 color=0
-- after setColor(2) --
Circle center=(0,0) radius=4 color=0
Circle center=(8,0) radius=4 color=2
Circle center=(16,0) radius=4 color=0
Circle center=(0,8) radius=4 color=0
Circle center=(8,8) radius=4 color=0
Circle center=(16,8) radius=4 color=0
In this code:
Circle& Collection::getCircleAt(const Point &p)
{
for (int i = 0; i < count; i++)
if (circles[i]->contains(p))
return *(circles)[i];
}
when none of the circles contains p, no return statement is executed. That means the return value of the function is undefined when that happens.
There's several ways to fix this. The simplest one is to return a pointer instead of a reference, and return nullptr when no circle was found:
Circle* Collection::getCircleAt(const Point &p)
{
for (int i = 0; i < count; i++)
if (circles[i]->contains(p))
return circles[i];
return nullptr;
}
Another solution that is being suggested here, is to throw an exception. However, I do not recommend this, because exceptions are for exceptional cases. There's nothing exceptional about getCircleAt() not finding a circle at some given position. It's something normal. It's not an error.
A better solution (in my opinion) is to change the API completely and instead return the index of the circle, not the circle itself. When no circle was found, return -1:
int Collection::getIndexAt(const Point &p)
{
for (int i = 0; i < count; i++)
if (circles[i]->contains(p))
return i;
return -1;
}
Then add an operator [] overload to get access to the circles by their index:
class Collection
{
public:
// ...
Circle& operator [](size_t index)
{
return *circles[i];
}
};
The caller of the function would then need to check if the circle was found:
auto index = g.getIndexAt(p);
if (index >= 0) {
g[index].setColor(2);
cout << "-- after setColor(2) --" << endl;
g.print();
} else {
cout << "-- circle not found at position --" << endl;
}
As a side note, your Collection destructor is not correct. You are not freeing the circles you allocated. You're only freeing the dynamic array. You need:
Collection::~Collection()
{
// Free each circle.
for (int i = 0; i < count; ++i)
delete circles[i];
// Free the array.
delete[] circles;
}
Another (unrelated) issue in your code, is that you are providing default argument values for your Circle::Circle() constructor in the implementation of the constructor, not in its declaration. You should change that. Default arguments should be specified in the declaration. So do this instead:
class Circle {
public:
Circle(int x = 0, int y = 0, int r = 0, int color = 0);
And remove the default argument values from the implementation:
Circle::Circle(int x, int y, int r, int color)
Furthermore, unless this was an exercise in memory allocation and pointers, you should switch to std::vector<Circle> instead of using pointers with new and delete. Manual memory management is error-prone. For example, you got it wrong in your Collection destructor above and were leaking memory. Using a standard container like vector will take care of memory management for you. A good rule of thumb is to never use new and delete unless you really have to.
Collection::getCircleAt does not return in all cases. This is undefined behaviour and can result in some really bizarre behaviour even if the error is not triggered. A function with a non-void return type must return ion every path. This is beaten to death in the comments.
What hasn't been covered is how to fix it.
Option 1: Throw an exception
Circle& Collection::getCircleAt(const Point &p)
{
for(int i=0;i<count;i++)
if(circles[i]->contains(p) )
return *(circles)[i];
throw std::runtime_error("No circle at P")
}
This incurs a significant performance penalty if p is frequently not associated with a circle. As in it is not exceptional behaviour. You only want to use exceptions for exceptional behaviour. If there should be a Circle at p and it's a rare and unusual event worth of special reporting when there isn't, us an exception.
g.getCircleAt(p).setColor(2);
will utterly fail if there is no Circle, suggesting this is a good choice here. You can catch and handle the exception here, leave it for a another function up the call stack to deal with (there aren't any in the asker's example) or let the program crash and harvest the message bundled with the exception to see why. Read your text's section on exception handling for more information.
If no Circle at p is a regular expected event, exception is probably the wrong tool. Proceed to option 2
Option 2: Return a canary value
Circle* Collection::getCircleAt(const Point &p)
{
for(int i=0;i<count;i++)
if(circles[i]->contains(p) )
return (circles)[i];
return nullptr;
}
Note the return type has been changed to a pointer so that it is legal to return a null. Never put yourself in a position where you are returning a null reference even if the compiler will let you. Null references are a nasty surprise no one should have to deal with. The pointer at least gives you some warning that a null may come your way, so watch out:
g.getCircleAt(p)->setColor(2);
Just dereferenced and accessed a null pointer. Kaboom. If the program did not crash, and it doesn't have to, the program just went insane. You need to do more. For example,
Circle * c = g.getCircleAt(p)
if (c != nullptr)
{
c->setColor(2);
}
else
{
// do error handling
}
You the programmer now have to do all of the error handling yourself, but you have total control and you know the costs. If it's not important, keep calm and and carry on. If it needs to be dealt with, deal with it.
Option 2A: std::optional
optional formalizes the canary value. You can test it to make sure you got a response and then carry on. First, some documentation: http://en.cppreference.com/w/cpp/utility/optional
optional literally just showed up in the C++17 standard revision. I think it was formally ratified in September or October, so it may not be available to you unless you are using the very latest of compilers.
In fact, I do not have such a compiler available and can't provide code that I've tested and know will work at the moment. That's why this is Option 2A. You're on your own with this one. I haven't had a chance to play with it yet.
I have a simple point class:
class Point {
public:
Point(const double, const double);
/** This constructor creates invalid point instance **/
Point();
~Point();
const double x;
const double y;
/** This returns true if one of the values is NaN **/
bool isInvalid() const;
/** Returns true if coordinates are equal **/
bool equal(const Point& p) const;
};
Values x and y are const so that I can be sure they never change. They are supposed to be always constant. The problem is I can't assign to variables holding Point:
Point somePoint;
... meanwhile, things happen ...
//ERROR: use of deleted function 'Point& Point::operator=(const Point&)'
somePoint = Point(x, y);
I understand that assigning is a problem because somePoint.x = something is forbidden. I need to use point to hold last point value during rendering:
Point lastPoint;
PointInGraph* point = graphValues.last;
while((point = point->next())!=nullptr) {
// calculate pixel positions for point
double x,y;
...
if(!lastPoint.isInvalid())
drawer.drawLine(round(lastPoint.x), round(lastPoint.y), round(x), round(y));
// ERROR: use of deleted function 'Point& Point::operator=(const Point&)'
lastPoint = Point(x, y);
}
So does const in class properties simply make any variable of that class type const as well? Or is there a workaround?
It's not possible. It would require modifying const values.
Instead of making x and y themselves const, make them non-const but provide a const interface to them, i.e. by making them private and providing const getters.
Instead of making the variables const you could just not provide any way for the user to change the values by:
making them private
only allowing assignment to a another instance as the only way to change the values.
You can see how this would work in the following example:
#include <iostream>
class Point {
public:
Point(const double x_ = 0, const double y_ = 0) : x(x_), y(y_) {}
double getX() const { return x; }
double getY() const { return y; }
private:
double x;
double y;
};
int main()
{
Point a{1,5};
Point p;
p = a;
std::cout << p.getX() << ", " << p.getY(); // no error here
//p.x = 5; // error here now
//p.y = 7; // error here now
}
Live Example
If you un-comment the last two lines you will get an error to prove that you cannot change x and y.
Actually it's, of course, possible by directly technique called cast away constness which is a known loophole in const mechanic in C++. Basically I can abuse the fact that const int* can be assigned to int* variable. The possible solution:
Point& Point::operator=(const Point& p)
{
*((double*)&x)=p.x;
*((double*)&y)=p.y;
}
This is of course not recommended, but I'm not the one of the people who think that knowledge is dangerous.
This is a general question on programming style. Let's say I have an object Line which has some methods and private variables Point point_a_ and Point point_b_. Let's say that at some point I need to change the position of the two points. What programming style would you prefer between the following cases? They all do the same thing (or should do: I didn't compile, but seems pretty straightforward).
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";
}
CASE 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";
}
CASE 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";
}
Any feedback is greatly appreciated!!
Thanks!
[EDIT] Speed is paramount in my software!
In this simple scenario I might just use public member variables.
Otherwise I would provide getters that return a const reference and matching setters.
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;
};
Case three is totally out because it completely violates principles of encapsulation. Case two does as well, to a slightly lesser extent. I would prefer option one, but did you consider possibly making the points immutable and forcing you to create a new object when it changes?
Also to be pedantic if I remember correctly from many years ago, a line technically extends infinitely in both directions. You're actually representing a line segment.
Case 2 is no better than public member variables. In particular, it doesn't encapsulate anything.
Case 3 makes ownership of the points unclear. Consider what happens if your points are local variables in the calling function, and then they go out of scope. It also offers no benefits over public member variables.
So out of these three options, Case 1 is the cleanest, IMO. Other options are:
simply use public member variables.
make Line immutable.
use set and get functions.
I would choose either case 1 or an immutable Line class.
Case 2 allows changes to the Point objects without the knowledge of their containing line. At some point, you might need the line to know if its points have been changed.
Case 3 either makes the Line object dependent on the lifetime of the Points, or makes the Line the owner of the points, which is not clear from the API.
An immutable Line would allow you to create a new Line object with new points.
Case 4 -- Prefer passing by const reference rather than value (or pointer):
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_
};
This is better in that in enforces encapsulation - the only way to change the variables held in the class after construction is a specific mutator method.
The accessors return a const reference, so they cannot be modified (copies made from these can be).
The class as a whole is made const-correct.
References are arguably better than pointers in this instance because they are guaranteed (unless you specifically break that guarantee) to not be NULL.
Consider another option:
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";
}
I used the name Segment as per suggestion above. This style is closer to functional programming style. Segment is immutable unless you explicitly reset it with a common assignment syntax. Distance is not a member function because it can be implemented in terms of Segment's public interface.
Regards,
&rzej