0

I am learning about copy elision and tried out something to learn about it. But there is something unexpected happening with the below code:

template<typename T>
class AutoPtr
{
    T* m_ref;
    public:
    AutoPtr(T* ref)
    {
        printf("Cons called\n");
        m_ref = ref;
    }
    AutoPtr(const AutoPtr& autoPtr)
    {
        printf("Copy called\n");
        m_ref = autoPtr.m_ref;

    }
    ~AutoPtr()
    {
        delete m_ref;
    }
};

class Reference
{
    public:
        Reference()
        {
            printf("Reference created\n");
        }
        ~Reference()
        {
            printf("Reference deleted\n");
        }
};

AutoPtr<Reference> generateRes()
{
    Reference *ref = new Reference();

     //Scenario 1
    //AutoPtr<Reference> temp{ref};
    //return temp;

    //Scenario 2
    return AutoPtr<Reference>{ref};
}                                                                                                                       
int main()
{
    AutoPtr<Reference> obj1 = generateRes();
    return 0;
}

In the above code, I am trying 2 scenarios.

  1. Initializing a temp AutoPtr object and then returning it. Here, constructor is called when temp is initialized. But in the main function, when obj1 is initialized, constructor is not called.
  2. Directly returning temporary object. Here in the main function, obj1 is initialized and constructor is called.

Why doesn't scenario 1 call constructor for obj1? Is it some compiler optimization? I know that copy elision has happened and copy constructor is not called, but why is the normal constructor not called twice?

Rogmier
  • 70
  • 6
  • 1
    Why would you think that the constructor should be called twice? It's called on construction of `temp` and because of copy elision there is no second object to call a constructor for. – Stefan Riedel Jan 23 '23 at 15:21
  • @StefanRiedel I understand that because of copy elision, copy constructor would not be called but since obj1 is entirely new object, its constructor should be called right? – Rogmier Jan 23 '23 at 15:26
  • 2
    @Rogmier No, the whole point of copy elision is that the two objects are merged to be considered just one single object. If you print the value of `this` in the constructor and compare it to the value of `&obj1` in `main`, you will see that they are the same object. – user17732522 Jan 23 '23 at 15:28
  • 4
    @Rogmier No, `AutoPtr obj1 = generateRes();` is not an assignment but an initialization. The job of copy-elision is exactly to make sure there is no second object the content of the returned value has to be copied or moved to. Construction will take place right at the destination object. – Stefan Riedel Jan 23 '23 at 15:29
  • 2
    `AutoPtr operator=(const AutoPtr& autoPtr)` -- Why are you returning a brand new object? You should be returning a reference to the current object: `AutoPtr& operator=(const AutoPtr& autoPtr)` – PaulMcKenzie Jan 23 '23 at 15:29
  • Also your `operator=` never performs an assignment, and only _sometimes_ returns a value at all. (And your copy constructor is built to guarantee double deletions all over the place.) – Nathan Pierson Jan 23 '23 at 15:31
  • Note that if you're observing this in a debugger, the apparent timeline of your source code and the actual timeline of the executed code (where copying is elided) don't necessarily match up. – molbdnilo Jan 23 '23 at 15:37
  • Does this answer your question? [What are the basic rules and idioms for operator overloading?](https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading) – Richard Critten Jan 23 '23 at 15:39
  • @Rogmier `if(m_ref == autoPtr.m_ref)` -- What do you return if the `if` statement is `false`? Your code will exhibit undefined behavior if this scenario happens. – PaulMcKenzie Jan 23 '23 at 15:39
  • 1
    What "normal constructor" do you expect called twice? Your second scenario uses Return Value Optimization (RVO, guaranteed since C++17), the first one may be subject to Named RVO (NVRO), which is not guaranteed, but has the same effect if it occurs. – Yksisarvinen Jan 23 '23 at 15:45
  • I get that copy assignment is done wrongly. It is not part of my query, so please ignore it. – Rogmier Jan 23 '23 at 15:50
  • So, I got to know that scenario 1 is NRVO and scenario 2 is RVO. Do they act the same in this question? – Rogmier Jan 23 '23 at 15:58

1 Answers1

2

I guess the part you missed about copy elision is what it implies. If no copy-construction takes place, what does the new (caller-side) object get constructed from?

No copying (or moving) implies there is only one object. That means construction is done right at the destination, i.e. constructing the return value inside the function == constructing the returned value outside.

This has also nothing to do with your copy-assignment operator AutoPtr operator=(const AutoPtr&), since AutoPtr<Reference> obj1 = generateRes(); is an initialization.

Stefan Riedel
  • 796
  • 4
  • 15