admin管理员组

文章数量:1129439

I have a question regarding the arrow operator's overload process. The overloading makes it seem like it is a unary operator, while the usage makes it appear as a binary operator. The return value isn't intuitive either. Only a raw pointer is returned.

#include <iostream>

template <typename T>
class Auto_ptr1
{
    T* m_ptr {};
public:
    // Pass in a pointer to "own" via the constructor
    Auto_ptr1(T* ptr=nullptr)
        :m_ptr(ptr)
    {
    }

    // The destructor will make sure it gets deallocated
    ~Auto_ptr1()
    {
        delete m_ptr;
    }

    // Overload dereference and operator-> so we can use Auto_ptr1 like m_ptr.
    T& operator*() const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
};

// A sample class to prove the above works
class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    void sayHi() { std::cout << "Hi!\n"; }
};

void someFunction()
{
    Auto_ptr1<Resource> ptr(new Resource()); // ptr now owns the Resource

    int x;
    std::cout << "Enter an integer: ";
    std::cin >> x;

    if (x == 0)
        return; // the function returns early

    // do stuff with ptr here
    ptr->sayHi();
}

int main()
{
    someFunction();

    return 0;
}

In the example ptr->sayHi(), one might expect the entire expression ptr-> to be replaced with the raw pointer, but apparently, only ptr is replaced. Anything that follows is coherent with intuition. The intermediate step is what bugs me. Is the overloading process of the arrow operator an exceptional case?

It's just a theory question.

I have a question regarding the arrow operator's overload process. The overloading makes it seem like it is a unary operator, while the usage makes it appear as a binary operator. The return value isn't intuitive either. Only a raw pointer is returned.

#include <iostream>

template <typename T>
class Auto_ptr1
{
    T* m_ptr {};
public:
    // Pass in a pointer to "own" via the constructor
    Auto_ptr1(T* ptr=nullptr)
        :m_ptr(ptr)
    {
    }

    // The destructor will make sure it gets deallocated
    ~Auto_ptr1()
    {
        delete m_ptr;
    }

    // Overload dereference and operator-> so we can use Auto_ptr1 like m_ptr.
    T& operator*() const { return *m_ptr; }
    T* operator->() const { return m_ptr; }
};

// A sample class to prove the above works
class Resource
{
public:
    Resource() { std::cout << "Resource acquired\n"; }
    ~Resource() { std::cout << "Resource destroyed\n"; }
    void sayHi() { std::cout << "Hi!\n"; }
};

void someFunction()
{
    Auto_ptr1<Resource> ptr(new Resource()); // ptr now owns the Resource

    int x;
    std::cout << "Enter an integer: ";
    std::cin >> x;

    if (x == 0)
        return; // the function returns early

    // do stuff with ptr here
    ptr->sayHi();
}

int main()
{
    someFunction();

    return 0;
}

In the example ptr->sayHi(), one might expect the entire expression ptr-> to be replaced with the raw pointer, but apparently, only ptr is replaced. Anything that follows is coherent with intuition. The intermediate step is what bugs me. Is the overloading process of the arrow operator an exceptional case?

It's just a theory question.

Share Improve this question asked Jan 8 at 9:04 user29104083user29104083 112 bronze badges New contributor user29104083 is a new contributor to this site. Take care in asking for clarification, commenting, and answering. Check out our Code of Conduct. 8
  • 1 When overloading operators in C++, one of the guidelines is "no surpises". And your overload of "->" is not surprising, it makes your "pointer like type" do a "dereference". std::unique_ptr/std::shared_ptr and iterators do the same (they also overload ->). So I don't see a real issue with your code (other than the fact that std::unique_ptr already exists). – Pepijn Kramer Commented Jan 8 at 9:08
  • 5 What do you mean when you say "replaced"? There's no "replacement" in the expression ptr->sayHi(). Instead it's equivalent to ptr.operator->()->sayHi(). In other words, the operator->() function is called on the ptr object (which isn't a pointer), it returns a pointer to a Resource object, and the pointer is dereferenced to get the actual Resource object and the sayHi() function is called on that Resource object. – Some programmer dude Commented Jan 8 at 9:08
  • 1 When overloading -> it is considered a unary operator because what appears on the right hand side is not an expression (technically it's an identifier expression, rather than a regular expression). An operator is binary if it combines two regular expressions. – john Commented Jan 8 at 9:43
  • What is the code in the above comment supposed to mean? – Weijun Zhou Commented Jan 8 at 9:56
  • 1 Not sure if the question is a duplicate, but the answer A: How arrow-> operator overloading works internally in c++? applies (it even starts by saying "The operator-> has special semantics"). Also related: Why overloaded de-reference operator doesn't work the same way as the arrow operator? – JaMiT Commented Jan 8 at 10:21
 |  Show 3 more comments

2 Answers 2

Reset to default 2

Yes, the arrow operator is an exceptional case of sorts. This is because the built-in -> operator is itself an exceptional case. For all other binary operators, the arguments are expressions that designate objects, so x ⊕ y means the same as x.operator⊕(y) (⊕ is any non-exceptional binary operator).

In x->y however, y is not an object, but an identifier that designates a class member. One cannot pass such a thing as a parameter to a user-defined function, x.operator->(y) would make no sense. So x->y is made to mean x.operator->()->y instead, in order for x to behave just like a pointer. As a consequence, the overloaded operator-> is required to be unary, not binary.

You can show that it is a unary operator by explicitly calling it:

#include <iostream>

class MyClass
{
public:
    int A = 0;

    int* operator->() { return &A; };
};

int main()
{

    MyClass object;
    object.A = 10;

    std::cout << "object.A: " << object.A << "\n";
    
    //  Explicitly call the -> operator, and de-reference the int pointer that is returned
    std::cout << "object->: " << *object.operator->() << "\n";

    return 0;
}

Keep in mind that returning a class member is not the normal 'expected' use of a -> operator, this is just for demonstration purposes.

本文标签: