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)