I have a program with two classes, one for points and one for a rectangle. I need to make two objects of point class within my rectangle class and make them private. I can't figure out how to use the rectangle constructor to make the points with the points constructor, every time I do so I get the error "no default constructor exists for class "Point2d"". Even when I make a default constructor I still get this. I'm at a loss here.
#include <iostream>
#include <cmath>
using namespace std;
class point2D {
private:
int x = 0;
int y = 0;
public:
int getX() {
return x;
}
int getY() {
return y;
}
point2D(int ax, int ay) {
x = ax;
y = ay;
}
};
class rectangleType {
private:
point2D p0;
point2D p1;
public:
int getX0() {
return p0.getX();
}
int getY0() {
return p0.getY();
}
int getX1() {
return p1.getX();
}
int getY1() {
return p1.getY();
}
int getWidth() {
return abs(getX1() - getX0());
}
int getLength() {
return abs(getY1() - getY0());
}
int getPerimeter() {
return 2 * (getWidth() getLength());
}
int getArea() {
return (getWidth() * getLength());
}
rectangleType(int ax0, int ay0, int ax1, int ay1) {
point2D p0(ax0, ay0);
point2D p1(ax1, ay1);
}
};
CodePudding user response:
Members and base classes not otherwise specifically initialized in the base member initialization list require provision of default construction. In it's simplest form, given this:
class S
{
public:
S(int) {}
};
this is not possible:
S s;
because the only ctor (default copy-ctor notwithstanding) defined requires an int
argument and none was provided. This is, perhaps, intentional by whomever designed S
. It may not make sense to have an S
without that require argument.
Carrying this forward to your specific circumstance, you have a class, point2D
, whose only defined course of construction (default copy-ctor notwithstanding) is this:
point2D(int ax, int ay)
Like S
in our previous example, this means something like this:
point2D pt;
is not possible. But that is exactly what is happening here (along with several other problems)
rectangleType(int ax0, int ay0, int ax1, int ay1)
// here, before entering the function body below, p0 and p1
// must be successfully constructed *somehow*
{
point2D p0(ax0, ay0);
point2D p1(ax1, ay1);
}
Before the constructor body is entered, all base classes (of which you have none), and member variables (of which you have two: p0
and p1
), must be constructed. Since you are not directly initializing your p0
and p1
members in a member initialization list, the compiler attempts to find default constructors (takes no arguments, or has sufficient default values for all arguments declared). It can't find one. By providing a non-default ctor as you have, you've declared "this is the way these should be constructed". The compiler is therefor saying it could not find a way to create these things successfully.
In your case, providing such a constructor, while possible, isn't necessarily the proper (and certainly not the only) solution. Yes, you can, but it turns out all that does is enhance later issues. For example that will allow this to compile:
rectangleType(int ax0, int ay0, int ax1, int ay1)
{
point2D p0(ax0, ay0);
point2D p1(ax1, ay1);
}
But now you have another problem. Your members p0
and p1
are not the ones you see above. All the code above does is declare two more id's with the same names as your members (thereby shadowing the latter into obscurity), construct them, discard them on function exit, and ultimately leave you with your two members default-initialized, but not with the provided arguments. Shortly thereafter, you scratch your head and wonder went wrong.
You could still provide that default-ctor for point2D
(which you didn't want to before, but now feel somewhat 'forced' to), and then do this:
rectangleType(int ax0, int ay0, int ax1, int ay1)
{
p0 = point2D(ax0, ay0);
p1 = point2D(ax1, ay1);
}
But now we're just piling on the rubbish, and an anti-pattern is forming. Now we're default-constructing our p0
and p1
members using a ctor we didn't think we needed (and perhaps even purposely didn't want), and then were discarding those efforts by constructing two more point2D
objects and using copy-assignment to reap them.
There is a better way
Member Initialization
The language provides a way of telling telling the compiler, "before entering my constructor body, first construct my base class (if any) and members like this" :
rectangleType(int ax0, int ay0, int ax1, int ay1)
: p0(ax0, ay0)
, p1(ax1, ay1)
{
}
The syntax may seem a bit odd, but apart from the leading colon you've seen the rest before. In fact, you were even using it before in your prior code, but in the wrong place, and for the wrong target variables. This syntax says "construct these members like this, then enter the class constructor function body."
Another Example
That isn't the only place you can (or should) use this feature of the language. For example, your point2D
constructor looks like this:
point2D(int ax, int ay)
{
x = ax;
y = ay;
}
But now you know you can also do this:
point2D(int ax, int ay)
: x(ax)
, y(ay)
{
}
Granted, it won't matter much, and any compiler with a grain of sanity will generate similar/identical code in the above trivial usage of this feature, but it stresses a larger overall theme. Where possible, use member-initialization lists to construct members. It is generally more efficient, and in many cases massively so.
A Special Note About Ordering
I mention this only because it is often relevant, ,and can be the cause of some interesting behavior you were otherwise not expecting. When using a member initialization list, the order of construction of the members is not dictated by the order of the list; it is dictated by the order of the member declarations. An example to show what I mean.
In your class, points p0
and p1
are declared in that specific order, top-down. Using our newly discovered member-initialization syntax, what happens when we do this (note the order in the list of members):
rectangleType(int ax0, int ay0, int ax1, int ay1)
: p1(ax1, ay1)
, p0(ax0, ay0)
{
}
This looks like p1
will be constructed before p0
. That is not the case. The order of declaration in the class is what matters. Since the class as this:
private:
point2D p0;
point2D p1;
That means p0
will be constructed first, then p1
, regardless of the order in the member-initialization list. This can lead to some interesting behavior, especially when you're not expecting it. In your trivial case it wouldn't overall matter, but it is worth noting nonetheless.
CodePudding user response:
Your error tells you that you have to create a default constructor for the points class, e.g.
point2D() {}
or if you want to move the x & y initialization to the constructor
point2D()
: x { 0 }, y { 0 } {}
It happens because you've created a specialized constructor that takes two parameters. If not for that, the compiler would have generated a default constructor for you. :)
As for the initialization in the rectangle class try this:
rectangleType(int ax0, int ay0, int ax1, int ay1)
: p { { ax0, ay0 } }, p1 { { ax1, ay1 } } {}
Also, to remove redundant assignment (thanks @user4581301) you could transform your current 2D(int ax, int ay)
into this:
point2D(int ax, int ay)
: x { ax }, y { ay } {}
Further reading:
- https://en.cppreference.com/w/cpp/language/constructor (again thank you @user4581301)
CodePudding user response:
your constructor should look like this:
rectangleType(int ax0, int ay0, int ax1, int ay1)
: p0 {ax0, ay0},
p1 {ax1, ay1}
{}
this is a member initialization list, you initialize values p0 and p1 by passing the arguments to their respective construcotrs