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();
}
(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
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();
the compiler will complain if we try this
    Gadget* p = &(g.giovanni); // incompatible types
or this
    int* p = &(g.giovanni);
    (*p).tellMe(); // *p does not have a tellme method
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;}
};
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();
}
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
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
}
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
}
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!
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()};
}
 

 
    
Top comments (0)