简体   繁体   中英

Point and Line class in c++?

I'm learning C++ (and programming in general) and I'm trying to make both a Point class and a Line class.

A line should be composed of 2 point objects.

Can the C++ gurus look over my work and tell me if this is how you should appropriately use pointers, references and classes?

class Point
{
    private:
        int x, y;
    public:
        Point() : x(0), y(0) {}
        Point(int x, int y) : x(x), y(y) {}
}

class Line
{
    private:
        Point *p1;
        Point *p2;
    public:
        Line(Point &p1, Point &p2) : p1(p1), p2(p2) {}

        void setPoints(Point &p1, Point &p2)
        {
            this->p1 = p1;
            this->p2 = p2;
        }
}

You should not be using pointers at all in your code. Use actual objects. Pointers are actually used quite rarely in C++.

class Point
{
    private:
        int x, y;
    public:
        Point() : x(0), y(0) {}
        Point(int x, int y) : x(x), y(y) {}
}

class Line
{
    private:
        Point p1;
        Point p2;
    public:
        Line(const Point & p1, const Point & p2 ) : p1(p1), p2(p2) {}

        void setPoints( const Point & ap1, const Point & ap2)
        {
            p1 = ap1;
            p2 = ap2;
        }
}

+1 what zabzonk said.

The time to use pointers, as you have used them, would be:

  1. You have several points
  2. You want to create lines using those points
  3. You want to change the values of the points and have the lines changed implicitly.

Step 3 above can be achieved if lines contain pointers to existing points. It introduces complexity though (eg, "when you destroy a Point instance, what happens to Line instances which contain pointers to the Point?"), which doesn't exist when (as zabzonk suggested) each Line contains its own Point values.

Whether or not the Point members of your line class are pointers or not creates a very different type of class. Using pointers will result in a classical CoGo style approach, which can be thought of as points being like nails in a board, and lines being rubber bands connecting those nails. Changing a point is like moving a nail, all the associated line works automatically follows, which is desirable in certain applications.

Using literal points means that the lines are all independant of one another, which is appropriate to other types of application.

This is a critical design decision for your classes at this stage.

Edit: As noted in other posts and the comment below, utilising simple pointers to achieve association between multiple lines and points also presents a serious potential problem. Specifically, if a point is deleted or moved in memory, all the pointers referring to that point must be updated. In practice, this tends to be overcome by using unique point IDs to look up a point rather than simple pointers. This also allows the CoGo structures to be easily serialized / saved. Thus our point class would have a static member to get a point based on ID, and our line class would have two point IDs rather than pointers.

I would prefer this...

class Point
{
    private:
        int x, y;
    public:
        Point() : x(0), y(0) {}
        Point(int x, int y) : x(x), y(y) {}
}

class Line
{
    private:
        Point p1;
        Point p2;
    public:
        Line(const Point &p1, const Point &p2) : p1(p1), p2(p2) {}

        void setPoints(const Point &p1, const Point &p2)
        {
            this->p1 = p1;
            this->p2 = p2;
        }
}

There is no need to use pointers in your Line class.

Also, the following line is incorrect:

Line(Point &p1, Point &p2) : p1(p1), p2(p2) {}

Why? You are assigning a Point & (p1) to a Point * (Line::p1), which is illegal. You'd want pointers there.

Your Point class has no way to modify its data. Not too useful...

A Line class for me would look something like this:

class Line
{
    private:
        Point p1, p2;

    public:
        Line()
        {
        }

        Line(Point start, Point end) :
            p1(start), p2(end)
        {
        }

        const Point &startPoint() const
        {
            return p1;
        }

        Point &startPoint()
        {
            return p1;
        }

        const Point &endPoint() const
        {
            return p2;
        }

        Point &endPoint()
        {
            return p2;
        }
};

You can now edit your line like this:

Line line;
line.startPoint() = Point(4, 2);
line.endPoint() = Point(6, 9);

A couple of things I noticed :

  • You can combine both of your point constructors into a single constructor with default values.
  • Your usage of pointers is quite unnecessary. Use the object itself.
  • You're using both pointers and references interchangeably. Don't mix them up, or see the last point.

I see little value in making Point's pointers (other than for irony value). Your Point takes 8 bytes on a 32 bit system (2 int's). A pointer takes 4 bytes. you're saving 4 bytes.

As for correctness, your Line constructor takes references, but you're assigning them to pointers. That shouldn't even compile. You're also doing the same thing in setPoints. It would be better to simply make the two points actual objects and copying their values.

class Line
{
    private:
        Point *p1; /* No memory allocated */
        Point *p2; /* No memory allocated */
    public:
        Line(Point &p1, Point &p2) : p1(p1), p2(p2) {}

        void setPoints(Point &p1, Point &p2) /* passed references to Point objects */
        {
            this->p1 = p1; /* asssiging Point objects to Point *! */
            this->p2 = p2; /* asssiging Point objects to Point *! */
        }
}

The setPoints() function wouldn't work - at first glance. It should be

            void setPoints(Point &p1, Point &p2) 
            {
                this->p1 = &p1; /* asssiging Point objects to Point *! */
                this->p2 = &p2; /* asssiging Point objects to Point *! */
            }

Then again, we don't have control over when p1 and p2 get destroyed. It's better to create this->p1 and this->p2 using the data in p1 and p2 so that the destructor has control over the memory

The compiler will give errors on this code:

  1. In the initialisation list of the Line constructor, you are assigning Point reference p1 to class member p1, which is a pointer to Point. To get this to compile, you should use Line(Point &p1, Point &p2) : p1(&p1), p2(&p2).
  2. The same problem occurs in the setpoints method. That shoudl be changed to this->p1 = &p1, etc.
  3. Minor issue: after the closing } of the class, put in a semicolon (;)

I think that concludes the syntax.

There is another issue with the code:

The p1 and p2 class members of Line are pointers to Point instances. Who creates these instances and who will delete them when they are not needed anymore? If you design your class as it is now, the code that creates a Line instance passes two Point instances to the constructor. If these Point instances are deleted before the Line is, the Line is left with two dangling pointers (pointers that no longer refer to a valid instance).

Also, the two Point instances that are now part of the Line can be modified from code outside of the Line class. This can lead to very undesirable situations.

In this situation I would declare the p1 and p2 members as Point (instead of a pointer to Point). That way, the Point instances that are passed to the constructor are copied to the class members. As long as the Line exists, the Point members will exist. They can only be changed by the line class itself.

Before asking about the language guru's opinion, start thinking about the design, in this case especially about the lifetime of your objects: does a point need to exist without a line? Do lines share points? Who creates a point, when does it stop to exist? Are two points with the same coordinates identical, or just equal (one might be red, the other might be blue)? ...

It appears that most here agree that you should use value-semantics on such a small code base. Sometimes, the problem requires the same object (ie Point) to be referenced by many sides, then use pointers (or references).

The choice between a pointer and a reference is still something else. I prefer using a reference when implementing aggregation (a line can't exist without it's endpoints), and a pointer when implementing a less 'intimate' relation.

Use Point objects in your Line class. The ownership of the points is unclear and you risk ending up with dangling pointers or leaked memory.

Your default constructor is a problem as you will rarely want to create a Point at (0,0). You are better off setting the default x,y values to something like MAXINT and asserting that any use of Point doesn't have MAXINT as one of its values. Have an is_valid() function so that clients can test. Your Line class can also have a precondition that its two points are not invalid. The pay off of not allowing a valid point to have a MAXINT value is that you can detect when Points have not been initialized properly, and you'll zap out some otherwise difficult to find bugs in the class usage.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM