2 Basic 4
Published:
Member Functions
Adding functions in a struct
struct Pt {
double x;
double y;
void print() {
std::cout << this->x << ",", y << std::endl
}
};
Refer object members
- Explicit:
y - Implicit:
this->x(thisis implicit pointer)
Refer member function
int main() {
struct Pt p1 = { 1.0, 2.0 }; // you can drop struct in cpp
p1.print();
(&p1)->print();
}
Class
Replace struct with class
class:privateby defaultstruct:publicby default
class Pt {
public:
double x;
double y;
void print();
};
Early cpp compiler translates member functions as
void Pt_print(struct Pt *this);
int main() {
Pt_print(&p1);
}
Stack Allocation
Constructor
Same name as the class
class Pt {
public:
double x; double y;
Pt() {
x = 4.0; y = 5.0;
}
};
int main() {
Pt p1; // constructor automatically invoked
}
When declared, allocate space and call the constructor
Can overload with different parameters and default parameters
Pt();
Pt(double _x);
Pt(double _x = 4.0);
Construction syntax
Pt p1(6, 7); // old cpp style
Pt p1(); // does NOT compile, taken as a FUNCTION PROTOTYPE
Pt p1 = {6, 7}; // C style
Pt p1 {6, 7}; // modern cpp
Pt p1; // implied no argument
Destructor
Use ~class_name (negation)
~Pt() {
std::cout << "bye" << std::endl;
}
- Called when the object goes out of scope
- If enclose object in
{}, will go away early - Always called when you leave the scope (exception)
- If enclose object in
- Constructor:
malloc(),fopen() - Destructor:
free(),fclose()
Heap Allocation
In cpp, void * cannot be assigned as a regular pointer, unless you cast it
Pt *p2 = (Pt *)malloc(sizeof(Pt));
p2->print();
free(p2);
Constructor and Destructor did not involve, since malloc() and free() are for void*
new() and delete()
Wrapper around malloc() and free()
Pt* p2 = new Pt {6, 7};
p2->print();
delete p2;
- No
deletein Java
Array Allocation
Pt* p3 = new Pt[5] {{6, 7}, {8, 9}, {10}};
delete[] p3;
- Allocate for an array of 5 (80 bytes)
- On the heap, an 8-byte integer pads in front to hold
5 - Can access through
((int_64_t *)p3) - 1
- On the heap, an 8-byte integer pads in front to hold
- Each constructor is called
- At
delete[], each destructor called- Passing
p3(pointer to the first element) - If not
delete[], will leak 88 bytes!
- Passing
Why doesn’t cpp do this when allocating a single object? Efficiency.
Value vs Reference
Passing by Value
void transpose(Pt p) { // no hi
double t = p.x;
// something
p.print(); // (5, 4)
} // bye
int main() {
Pt p4 = {4, 5};
p4.print(); // (4, 5)
transpose(p4); // passing by value
p4.print(); // (4, 5), unchanged
}
When you pass by value, p is copied
- Our own constructor not called. An internal Copy Constructor called!!!
- After
transpose()done, its destructor called
Passing by Reference in C
void transpose(Pt* p) {
double t = p->x;
// something
}
int main() {
Pt p4 = {4, 5};
p4.print(); // (4, 5)
transpose(&p4); // passing by pointer
p4.print(); // (5, 4)!
}
- No copying
When passing
Pt *, technically it’s still passing an address value, simulating reference
Passing by Reference in Cpp
CPP supports this natively. Change parameter from Pt to Pt&
void transpose(Pt& p) { // type Pt&
double t = p.x;
}
Can use reference as if using the value.
- Automatic dereferencing
- Think
pas a reference (alias) forp4 - Pointer, but without deref
When seeing
transpose(p4)in cpp, less readable. Can’t tell if value/reference
Pt p1 {100. 5}, p2 {200, 5};
Pt* p; // we declare a pointer variable, uninitialized
p = &p1; // p points to p1
p = &p2; // p now points to p2
Pt& r = p1; // we create a reference to p1 named r
// Pt& r; // error: references must be initialized when declared
r = p2; // this does NOT change r to refer to p2
// instead, it is equivalent to p1 = p2;
p1.print(); // will print "(200,5)"
- You have to bind a reference to an object at creation
- aka stuck pointer
- In reassignment, will reassign the referenced objects
- For all purposes of
r, it meansp1 - but avoids making a separate object
- For all purposes of
- Always stack-allocated
Copy Constructor
Regular constructor that takes another object reference ONLY CALLED AT INITIALIZATION
Pt(Pt& orig) { // copy constructor
x = orig.x;
y = orig.y;
std::cout << "copy" << std::endl;
}
When copying by value, copy constructor invoked
- If not defined, compiler will generate one for you that mimics the C behavior
cpp invented reference for copy constructor If you call
Pt(Pt other), the copy constructor has to be recursively called…
const
If you take something by const reference, the value won’t be changed
Pt(const Pt& orig);
If you declare const on p4, but:
void print();
const Pt p4; // p4 won't be modified
p4.print(); // WON'T COMPILE
this.print() will execute Pt* this = &p4;, but const Pt* &p4 can’t be converted to regular Pt* this!
print()suggests possible mutation
Need to declare as const member function!!!
void print() const;
- implies
thisas aconst Pt* - If member function not modifying objects, always declare as
const.
Direct Invocation
Directly make a copy
{
// copy construction semantics
Pt p5 {p4};
Pt p5(p4);
Pt p5 = {p4}; // C
Pt p5 = p4; // C init
// COPY ASSIGNMENT!
Pt p5; p5 = p4;
}
Pass values of p4 to p5
Return by Value
Compile of -fno-elide-constructors -std=c++14
Also invokes copy constructor
When expand() returns by value, it creates a temporary unnamed object, which is again copy constructed to be assigned
Pt expand(Pt p) { // copy construction by value
Pt q; // construction
q.x = p.x * 2;
q.y = p.y * 2;
return q; // copy construction: return value
// q destroyed
} // p destroyed
int main() {
const Pt p4;
cout << "*** p6: returning object by value" << endl;
{
Pt p6
{
// tmp created here // copy of q when expand() returns
expand(p4)
}; // copy construction on p6
// tmp destroyed
} // p6 destroyed
cout << "Thats all folks!" << endl;
} // p4 destroyed
g++ -fno-elide-constructors -std=c++14 # ...
hi # p4's constructor
*** p6: returning object by value
copy # call expand()
hi # q's constructor
copy # copy of tmp
bye # q's destructor
copy # p6 copy from tmp
bye # tmp or p
bye # tmp or p
bye # p6 destroyed
Thats all folks!
bye # p4's destructor
3 copy calls:
- Passing
p4toexpand(), copying top expand()returnq, creating atmpobject (different fromq)main()allocates space for suchtmp.
- Copy of
tmptop6
5 destructor calls
qdestroyed afterexpand()returnstmpdestroyed afterp6constructedpdestroyed afterexpand()returnsp6destroyedp4destroyed
Now compile without -no-elide-constructors
hi # p4's constructor
*** p6: returning object by value
copy # p construct
hi # q construct
bye
bye
Thats all folks!
bye # p4's destructor
p6 and tmp not constructed
- When returning object, it is typically constructed in the stack frame of the caller
main() - When compiler is allowed to
elide, this temporary (just used to copy into another object), is omitted p6,tmp,qget collapsed to one
By cpp 17, elide is forced
Copy Assignment
Assign one struct to another, so each member is assigned
int main() {
const Pt p4;
cout << "*** p7: copy assignment" << endl;
{
Pt p7 {8,9};
p7.print();
p7 = p4;
p7.print();
}
cout << "That's all folks!" << endl;
}
hi // p4's constructor
*** p7: copy assignment
hi // p7's constructor
(8,9) // p7.print() before the assignment
(4,5) // p7.print() after the assignment
bye // p7's destructor
That's all folks!
bye // p4's destructor
Same behavior as C
You can use operator=() to define the copy assignment operator
- Invoked from
b = a; - Assignment is an expression with a value
Pt& operator=(const Pt& rhs) { x = rhs.x; // (this.)x = rhs.x y = rhs.y; std::cout << "op()" << std::endl; return *this; } - LHS comes as this
thisobject - RHS comes as parameter
rhs - It you return by value
Pt, it will make another copy
Compiler Default
- Constructor: generated if no constructor or copy constructor. Invoke member constructors
- Destructor: invoke member destructor
- Copy constructor: generated if not present. Will copy by member (invoke their CC)
- Copy assignment: same
