DEV Community

John Calabrese
John Calabrese

Posted on

C++ noob stuff: copies, return references, constructors.

I started to learn C++ for work, and it's been "fun". One of the first things I learned is passing function arguments by reference, instead of by value (or by passing a raw pointer). You can also return references, but I haven't seen this covered explicitly, so I wrote a small program to test things out. Here is a first version.

Gadgets

struct Gadget {
    // members
    int giovanni;
    int giorgio;
    // constructor
    Gadget(int giov, int gior): giovanni(giov), giorgio(gior) {}
    // a convenient method
    void tellMe() {
        cout << "giovanni: " << giovanni << ", " << &giovanni << "\n";
        cout << "giorgio: " << giorgio << ", " << &giorgio << "\n";
    }
};

int main() {
    Gadget g{-3,7};
    cout << "Defined a new Gadget g at " << &g << "\n";
    g.tellMe();
}
Enter fullscreen mode Exit fullscreen mode

(I'm using struct here, and class later, for no good reason)
Running it spits out something like this.

Defined a new Gadget g at 0x7ffee700d918
giovanni: -3, 0x7ffee700d918
giorgio: 7, 0x7ffee700d91c
Enter fullscreen mode Exit fullscreen mode

Interestingly, the address of g is the same as the one for g.giovanni. This is probably C++ being smart about memory. This seems to be ok. While the lines below are fine

    Gadget* p = &g;
    (*p).tellMe();
Enter fullscreen mode Exit fullscreen mode

the compiler will complain if we try this

    Gadget* p = &(g.giovanni); // incompatible types
Enter fullscreen mode Exit fullscreen mode

or this

    int* p = &(g.giovanni);
    (*p).tellMe(); // *p does not have a tellme method
Enter fullscreen mode Exit fullscreen mode

Widgets

Let's add a second class (this time a class with everything explicitly public).

class Widget {
public:
    // members
    int myInto;
    Gadget myGadget;
    // constructor
    Widget(int giov, int gior, int myo): myGadget(giov, gior), myInto(myo) {}
    // info
    void tellMe() {
        cout << "myInto: " << myInto << ", " << &myInto << "\n";
        myGadget.tellMe();}
    // getters: by value, by reference
    Gadget getGadget_val() {return myGadget;}
    Gadget& getGadget_ref() {return myGadget;}
};
Enter fullscreen mode Exit fullscreen mode

Let's write a main to use this.

int main() {
    Widget w{2,-3,77};
    cout << "defined new widget w at " << &w << "\n";
    w.tellMe();

    cout << "\ncalling tellMe from w.getGadget_ref()\n";
    w.getGadget_ref().tellMe();

    cout << "\ncalling tellMe from w.getGadget_val()\n";
    w.getGadget_val().tellMe();
}
Enter fullscreen mode Exit fullscreen mode

Running this will output something like this

defined new widget w at 0x7ffeec970910
myInto: 77, 0x7ffeec970910
giovanni: 2, 0x7ffeec970914
giorgio: -3, 0x7ffeec970918

calling tellMe from w.getGadget_ref()
giovanni: 2, 0x7ffeec970914
giorgio: -3, 0x7ffeec970918

calling tellMe from w.getGadget_val()
giovanni: 2, 0x7ffeec970908
giorgio: -3, 0x7ffeec97090c
Enter fullscreen mode Exit fullscreen mode

and we see that indeed the members of w.getGadget_val() have different memory addresses.

Sanity check

As confirmation, let's try modifying getGadget_val() and getGadget_ref(). Here's a complete snippet.

#include <iostream>
using std::cout;

struct Gadget {
    // members
    int giovanni;
    int giorgio;
    // constructor
    Gadget(int giov, int gior): giovanni(giov), giorgio(gior) {}
    // a convenient method
    void tellMe() {
        cout << "giovanni: " << giovanni << ", " << &giovanni << "\n";
        cout << "giorgio: " << giorgio << ", " << &giorgio << "\n\n";
    }
};

class Widget {
public:
    // members
    int myInto;
    Gadget myGadget;
    // constructor
    Widget(int giov, int gior, int myo): myGadget(giov, gior), myInto(myo) {}
    // info
    void tellMe() {
        cout << "myInto: " << myInto << ", " << &myInto << "\n";
        myGadget.tellMe();}
    // getters: by value, by reference
    Gadget getGadget_val() {return myGadget;}
    Gadget& getGadget_ref() {return myGadget;}
};

int main() {
    Widget w{2,-3,77};
    cout << "defined new widget w at " << &w << "\n";
    w.tellMe();

    Gadget g1 = w.getGadget_ref();
    g1.giovanni--;
    w.tellMe();
    // does not change w: g1 is a copy of w.myGadget!!

    Gadget& g2 = w.getGadget_ref();
    g2.giovanni--;
    w.tellMe();
    // works: notice when we create g2 we use Gadget&

    Gadget g3 = w.getGadget_val();
    g3.giovanni--;
    w.tellMe();
    // does not change w, as expected

    // Gadget& g4 = w.getGadget_val();
    // does not even compile, as RHS is temp
}
Enter fullscreen mode Exit fullscreen mode

This all makes sense, however the difference between g1 and g2 is subtle: it's only in the type we use. We could easily make that mistake and copy something without noticing (or viceversa). What if we don't want to allow copies? Or references? For this we can tinker with Gadget's constructors.

Constructors

Let's delete the copy and move constructors. We also need to delete getGadget_val as that implicitly uses those.

#include <iostream>
using std::cout;

struct Gadget {
    // members
    int giovanni;
    int giorgio;
    // constructor
    Gadget(int giov, int gior): giovanni(giov), giorgio(gior) {}
    Gadget(const Gadget&) = delete;
    Gadget(const Gadget&&) = delete;
    // a convenient method
    void tellMe() {
        cout << "giovanni: " << giovanni << ", " << &giovanni << "\n";
        cout << "giorgio: " << giorgio << ", " << &giorgio << "\n\n";
    }
};

class Widget {
public:
    // members
    int myInto;
    Gadget myGadget;
    // constructor
    Widget(int giov, int gior, int myo): myGadget(giov, gior), myInto(myo) {}
    // info
    void tellMe() {
        cout << "myInto: " << myInto << ", " << &myInto << "\n";
        myGadget.tellMe();}

    Gadget& getGadget_ref() {return myGadget;}
    // we had to eliminate the _val version
};

int main() {
    Widget w{2,8,77};
    w.tellMe();

    Gadget& g1{w.getGadget_ref()};
    g1.tellMe();

    // Gadget g2{w.getGadget_ref()};
    // does not compile: we deleted the copy constructor
}
Enter fullscreen mode Exit fullscreen mode

As we can see in the last line, we can't make a copy as the copy constructor has been deleted. Just for fun, let's keep the move constructor deleted, but not the copy constructor. This should allow us to make copies, right? Well, as it turns out this code does not compile:

// does not compile!
#include <iostream>
using std::cout;

struct Gadget {
    // members
    int giovanni;
    int giorgio;
    // constructor
    Gadget(int giov, int gior): giovanni(giov), giorgio(gior) {}
    Gadget(const Gadget&&) = delete;
    // a convenient method
    void tellMe() {
        cout << "giovanni: " << giovanni << ", " << &giovanni << "\n";
        cout << "giorgio: " << giorgio << ", " << &giorgio << "\n\n";
    }
};

class Widget {
public:
    // members
    int myInto;
    Gadget myGadget;
    // constructor
    Widget(int giov, int gior, int myo): myGadget(giov, gior), myInto(myo) {}
    // info
    void tellMe() {
        cout << "myInto: " << myInto << ", " << &myInto << "\n";
        myGadget.tellMe();}
    // getters: by value, by reference
    Gadget getGadget_val() {return myGadget;}
    Gadget& getGadget_ref() {return myGadget;}
};

int main() {
    Widget w{2,8,77};
    Gadget g1{w.getGadget_val()};
}
// does not compile!
Enter fullscreen mode Exit fullscreen mode

The reason being the copy constructor has been implicitly deleted by our deletion of the move constructor. To remedy, we need to be a bit more explicit about the copy constructor. For example by making it default.

#include <iostream>
using std::cout;

struct Gadget {
    // members
    int giovanni;
    int giorgio;
    // constructor
    Gadget(int giov, int gior): giovanni(giov), giorgio(gior) {}
    Gadget(const Gadget&) = default;
    Gadget(const Gadget&&) = delete;
    // a convenient method
    void tellMe() {
        cout << "giovanni: " << giovanni << ", " << &giovanni << "\n";
        cout << "giorgio: " << giorgio << ", " << &giorgio << "\n\n";
    }
};

class Widget {
public:
    // members
    int myInto;
    Gadget myGadget;
    // constructor
    Widget(int giov, int gior, int myo): myGadget(giov, gior), myInto(myo) {}
    // info
    void tellMe() {
        cout << "myInto: " << myInto << ", " << &myInto << "\n";
        myGadget.tellMe();}
    // getters: by value, by reference
    Gadget getGadget_val() {return myGadget;}
    Gadget& getGadget_ref() {return myGadget;}
};

int main() {
    Widget w{2,8,77};
    Gadget g1{w.getGadget_val()};
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)