简体   繁体   中英

C++ an issue with returning a temporary object

Hello I'm about a month old in C++ so please excuse me if this question is too trivial.

#include <iostream>
using namespace std;

class Point {
    int x,y;

public:
    Point(int x_val = 0,int y_val = 0) :x(x_val),y(y_val) { }
    Point(Point& copy) : x(copy.x), y(copy.y) { }

    void showLocation() const { cout << "[" << x << "," << y << "]" << endl; }
    friend Point operator+(const Point& p1, const Point& p2);
    friend Point operator-(const Point& p1, const Point& p2);

    Point operator-(Point& p2) {                    // (1)
        Point(x-p2.x, y-p2.y).showLocation();
        return Point(x-p2.x, y-p2.y);
    }
};

Point operator+(const Point& p1, const Point& p2) { // (2)
    Point pos(p1.x+p2.x, p1.y+p2.y);
    return pos;
}

Point operator-(const Point& p1, const Point& p2) { // (3)
    return Point(p1.x-p2.x, p1.y-p2.y);
}

int main() {
    Point p1(3,4);
    Point p2(2,5);

    (p1+p2).showLocation();
    //(p1-p2).showLocation();
    operator-(p1,p2);
}

So this is a simple code for practicing operator overloading - I simply created a point and overloaded + and - operators, both as a member of the class and a global function.

When I compile this code, however, I discovered that while (2) works, both (1) and (3) keep showing the error that I cannot see why:

q1.cpp:17:10: error: no matching constructor for initialization of 'Point' return Point(x-p2.x, y-p2.y); ^~~~~~~~~~~~~~~~~~~~~ q1.cpp:8:2: note: candidate constructor not viable: no known conversion from 'Point' to 'int' for 1st argument

As I understand it, both (1) and (3) are supposed to return a temporary Point object, which, according to Google search, should be equivalent to case (2).

Also, the error statement confuses me even more - I can't see any conversion occurring in the expression mentioned as problematic.

Point(x-p2.x, y-p2.y).showLocation();

This works fine, and case (2) also does, so I guess this is not a syntax issue. I couldn't see any problem other than the possibility of an issue regarding returning a temporary object (without naming it).

Thanks!

You can fix this by simply removing your copy constructor. It serves no useful purpose. Alternatively, you can fix your copy constructor by using const in the signature as per the default signature.

Point(Point const & copy) : x(copy.x), y(copy.y) { }

Without the const , the copy constructor cannot be used with a temporary instance as input.

Resources:

This:

Point(Point& copy) : x(copy.x), y(copy.y) { }

is a technically valid copy constructor , but it restricts the possible actual arguments.

A temporary or const object can't be bound to the Point& formal argument, because that's a reference to non- const , which would permit modifying the actual argument.

Instead you can write

Point( Point const& other ): x( other.x ), y( other.y ) {}

where I've also corrected the misleading argument naming.

But even easier, for this class you can just do

Point( Point const& other ) = default;

And absolutely easiest, just don't define a copy constructor at all, let the compiler do it for you.


Similarly, for maximum usability you should change

Point operator-(Point& p2) {                   // (1)

to

Point operator-(Point const& p2) const {       // (1)

The const for the argument means that it can now be called with const or temporary (rvalue) argument.

And the const for the member function itself, the last const , means that it can be called on a const object. The rules here are a bit subtle. In contrast to the restriction for the argument, without the const it can already be called on a temporary object (ie via an rvalue-expression).


General advice:

  • sprinkle const liberally just about everywhere it can be used,

  • let the compiler generate copy operations unless you're taking charge of copying , and

  • preferentially use data member of types where copying is safe, eg built-in types, smart pointers and standard library collections.

The reason for the error (in your case (1)) is

Point operator-(Point& p2) {                    // (1)
    Point(x-p2.x, y-p2.y).showLocation();
    return Point(x-p2.x, y-p2.y);
}

is that the Point(x-p2.x, y - p2.y) produces a temporary object. Returning that (semantically) requires creating a copy of the temporary, which requires a copy constructor that accepts a const argument. Your copy constructor does not accept a const argument.

The solutions are to either modify your copy constructor to accept a const parameter, or remove it completely (since the compiler will automatically generate a copy constructor that works correctly for you). In C++11, use Point(const Point &) = default; if needed, to force the compiler to generate the correct copy constructor.

Note: the standard requires the compiler to enforce the need for the correct copy constructor, even if the temporary is optimised out of existance. This helps improve portability of code to compilers that do not optimise out the temporary.

Another concern with your code is that you have two variants of operator-() .

Your cases (1) and (3) are not equivalent. The first generates a member function form of operator-() (which accepts one argument, since there is an implied this ) and the second is a non-member form which accepts two arguments.

When it sees an expression like p1-p2 (where p1 and p2 are both of type Point ), both versions of that operator-() are equally good candidates. The compiler will complain about ambiguity, since it has no reason to prefer one over the other. So a statement

(p1-p2).showLocation();

will not compile. I assume you have commented it out for that reason.

The solution to that is to use one form or the other of operator-() . Not both at once.

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