2
#include <iostream>
using namespace std;

class samp
{
    int *p;
    int len;
    int idx;  // idx denotes the number of elements currently in the array (allocated by p)

    public:
    samp()  
    {
        len=0;
        p=NULL;
        idx=0;
        cout<<"inside default constructor"<<endl;
    }
    samp(int len)
    {
        this->len=len;
        p=new int[len];
        idx=0;
        cout<<"inside parameterized constructor, p="<<p<<endl;
    }
    samp(const samp &s)
    {
        len=s.len;
        idx=s.idx;
        
        p = new int[len];
        for(int i=0;i<idx;i++){
            p[i]=s.p[i];
        }
        cout<<"inside copy constructor, p="<<p<<endl;
    }
    ~samp()
    {
        cout<<"inside destructor, clearing memory "<<p<<endl;
        delete[] p;
        p=NULL;

    }
    void insert(int a)
    {
        if(idx<len){  // if there is still some place left in the array
            p[idx]=a;
            idx++;
        }
    }
    void print()
    {
        for(int i=0;i<idx;i++){
            cout<<p[i]<<" ";
        }
        cout<<endl;
    }
    
};

samp f()
{
    samp s(3);
    int a;
    for(int i=0;i<3;i++){
        cin>>a;
        s.insert(a);
    }
    cout<<"now we will return an object from a function"<<endl;
    return s;
}

int main()
{
    samp s1;
    cout<<"now we will try to return an object from a function"<<endl;
    s1 = f();
    cout<<"function returned"<<endl;
    s1.print();
    return 0;
}

I am getting the following output and a double free error, because the same memory is being freed twice. Copy constructor is not getting called here because of copy-elision.

inside default constructor
now we will try to return an object from a function
inside parameterized constructor, p=0x5573aa164e80
now we will return an object from a function
inside destructor, clearing memory 0x5573aa164e80
function returned
0 0 -1441456112 
inside destructor, clearing memory 0x5573aa164e80

As the copy constructor is not getting called, I am getting garbage values as well.

When I use -fno-elide-constructors as a compiler flag, copy constructor is getting called, but the double memory free error still persists. I am getting an output like this:

inside default constructor
now we will try to return an object from a function
inside parameterized constructor, p=0x10c008
now we will return an object from a function
inside copy constructor, p=0x10c040
inside destructor, clearing memory 0x10c008
inside destructor, clearing memory 0x10c040
function returned
85 23 19
inside destructor, clearing memory 0x10c040

Codeblocks is somehow managing to print the values stored in the freed memory, but the same memory is getting freed twice, which I want to avoid.

The destructor is getting called twice here during function return, most probably because it is destructing the local object s of function f once and then destructing the copy object.

If we overload the = operator, the problem of double free disappears.

samp& operator=(const samp &s)
{
    len=s.len;
    idx=s.idx;
        
    p = new int[len];
    for(int i=0;i<idx;i++){
        p[i]=s.p[i];
    }
        
    cout<<"assigning, new p="<<p<<endl;
    return *this;
}

Then I get an output like this:

inside default constructor
now we will try to return an object from a function
inside parameterized constructor, p=0x6bc008
now we will return an object from a function
inside copy constructor, p=0x6bc040
inside destructor, clearing memory 0x6bc008
assigning, new p=0x6bc078
inside destructor, clearing memory 0x6bc040
function returned
85 23 19
inside destructor, clearing memory 0x6bc078

My question is, if a copy constructor cannot solve the problem of a memory location getting freed twice, why is it even called during returning an object from a function? Moreover, the code of the = operator overload and copy constructor are almost the same. How is the overloaded = operator solving the problem?

If I return an object from a function, it is highly probable that I will assign it to another object after function call. So do we need to overload the = operator every time we return an object having dynamically allocated memory from a function?

I know about STL vectors. I need to know the behavior of copy constructors in such circumstances.

Preetom Saha Arko
  • 2,588
  • 4
  • 21
  • 37
  • Use `std::vector p;` and let the compiler generate the correct one by `default`. – πάντα ῥεῖ Feb 24 '23 at 12:42
  • @πάνταῥεῖ I know about STL vectors. I need to know the behavior of copy constructors in such circumstances. – Preetom Saha Arko Feb 24 '23 at 12:43
  • 3
    The class does not handle the resources correctly with the synthesized assignment operator. Disable it by: `void operator=(samp const&) = delete;` – Eljay Feb 24 '23 at 12:46
  • 3
    `s1 = f();` Return value of `f()` and its `p` is copied bit-by-bit to a new instance with assignment operator, now both instances' `p` is pointing the same address on the memory. Both instances will try and free that memory when reached destructor. Assignment operator does not call copy-constructor. You need to overload assignment operator too in this case. – no more sigsegv Feb 24 '23 at 12:54
  • @Eljay Couldn't understand your point. Could you please elaborate a bit? – Preetom Saha Arko Feb 24 '23 at 13:02
  • @PreetomSahaArko the implicit assignment operator just blindly copies every member is called and that will of course copy the value of the `p` pointer instead of allocating a new pointer and copying the values pointed by `p`. Reads this: https://stackoverflow.com/questions/2779316/behaviour-of-the-implicit-copy-constructor-assignment-operator – Jabberwocky Feb 24 '23 at 13:13
  • @Jabberwocky Why will the implicit assignment operator be invoked even when I have overloaded it? – Preetom Saha Arko Feb 24 '23 at 13:15
  • @PreetomSahaArko no, of course not. – Jabberwocky Feb 24 '23 at 13:16
  • _If we overload the = operator, the problem of double free disappears._ Looks like you answered your own question. – Paul Sanders Feb 24 '23 at 13:25
  • [The Rule of Three](https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming)) (or Five) – Eljay Feb 24 '23 at 13:54

1 Answers1

4

You are conflating and mixing up copy constructors and assignment operators. Both must be defined in order to get correct RAII, and handle allocated memory correctly.

s1 = f();

This invokes the assignment operator. It was the missing assignment operator that resulted in the double-free here. The copy constructor had absolutely nothing to do with it, whatsoever.

Your copy constructor was doing its job just fine. A non-elided copy, when returning from a function, always invokes the copy constructor. If a copy is elided, nothing gets copied, so no copy-constructor is involved.

But whether the copy is elided, or not, this will always result in the assignment operator getting called. This is assigning the returned value to an existing object. Full stop. This requires invocation of the assignment operator. Copy constructors are irrelevant, completely, when assigning one object to another one. A failure to implement it resulted in the double-free.

Sam Varshavchik
  • 114,536
  • 5
  • 94
  • 148
  • If I return an object from a function, it is highly probable that I will assign it to another object after function call. So do we need to overload the = operator every time we return an object having dynamically allocated memory from a function? – Preetom Saha Arko Feb 24 '23 at 13:06
  • 1
    @PreetomSahaArko, if you have dynamic memory, you should generally have assignment operators. https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three – ChrisMM Feb 24 '23 at 13:14
  • 2
    The `=` operator should be overloaded whenever the default `=` operator will result in undefined behavior, if invoked. If one is certain that instances of the class will never be assigned to other instances, the `=` operator can be deleted and then the compiler will bark if one's assumptions are proven to be wrong. – Sam Varshavchik Feb 24 '23 at 13:21