CSE333(12-14): C++ related
Today I learn C++ again…
This is a great language.
Lecture 12 - C++ Constructor Insanity
Constructors
-
A class can have multiple constructors that differ in parameters, similar to JAVA.
-
C++ will automatically create a synthesized default constructor if you have no user-defined constructors.
-
If you define any constructors, C++ assumes you have defined all the ones you intend to be available and will not add any others.
-
Multiple Constructors (overloading)
-
Constructor with an initialization list
- Data members in initializer list are initialized in the order they are defined in the class, not by the initialization list ordering
// constructor with an initialization list
Point::Point(const int x, const int y) : x_(x), y_(y) {
std::cout << "Point constructed: (" << x_ << ",";
std::cout << y_<< ")" << std::endl;
}
Copy Constructors
- C++ has the notion of a copy constructor (cctor)
- Used to create a new object as a copy of an existing object
- Initializer lists can also be used in copy constructors (preferred)
Point::Point(const int x, const int y) : x_(x), y_(y) { }
// copy constructor
Point::Point(const Point& copyme) { // param: alias to a const object
x_ = copyme.x_;
y_ = copyme.y_;
}
void foo() {
Point x(1, 2); // invokes the 2-int-arguments constructor
Point y(x); // invokes the copy constructor
// could also be written as "Point y = x;"
}
The copy constructor is invoked if:
- Initialize an object from another object of the same type:
Point x; // default ctor
Point y(x); // copy ctor
Point z = y; // copy ctor
- Pass a non-reference object as a value parameter to a function:
void foo(Point x) { ... }
Point y; // default ctor
foo(y); // copy ctor
- Return a non-reference object value from a function:
Point foo() {
Point y; // default ctor
return y; // copy ctor
}
Compiler Optimization
The compiler sometimes uses a “return by value optimization” or “move semantics” to eliminate unnecessary copies
- Sometimes you might not see a constructor get invoked when you might expect it
Point foo() {
Point y; // default ctor
return y; // copy ctor? optimized?
}
Point x(1, 2); // two-ints-argument ctor
Point y = x; // copy ctor
Point z = foo(); // copy ctor? optimized?
Synthesized Copy Constructor
If you don’t define your own copy constructor, C++ will synthesize one for you
- It will do a shallow copy of all of the fields (i.e. member variables) of your class
- Sometimes the right thing; sometimes the
wrong
thing
Assignment
- Assignment != Construction
Point w; // default ctor
Point x(1, 2); // two-ints-argument ctor
Point y(x); // copy ctor
Point z = w; // copy ctor
y = x; // assignment operator
- Overloading the “=” Operator
Point& Point::operator=(const Point& rhs) {
if (this != &rhs) { // (1) always check against this
x_ = rhs.x_;
y_ = rhs.y_;
}
return *this; // (2) always return *this from op=
}
- Synthesized Assignment Operator
- If you don’t define the assignment operator, C++ will synthesize one for you
Destructors
C++ has the notion of a destructor (dtor):
- Invoked automatically when a class instance is deleted, goes out of scope, etc. (even via exceptions or other causes!)
- Place to put your cleanup code – free any dynamic storage or other resources owned by the object
- Standard C++ idiom for managing dynamic resources • Slogan: “Resource Acquisition Is Initialization” (RAII)
Point::~Point() { // destructor
// do any cleanup needed when a Point object goes away
// (nothing to do here since we have no dynamic resources)
}
Lecture 13 - C++ Class Details, Heap
Class Details
Rule of Three
If you define any of:
- Destructor
- Copy Constructor
- Assignment (operator=) Then you should normally define all three, can explicitly ask for default synthesized versions (C++11):
class Point {
public:
Point() = default; // the default ctor
~Point() = default; // the default dtor
Point(const Point& copyme) = default; // the default cctor
Point& operator=(const Point& rhs) = default; // the default "="
...
}
Dealing with the instanity
C++ style guide tip:
- If possible, disable the copy constructor and assignment operator. C++11 has direct syntax to indicate this:
class Point {
public:
Point(const int x, const int y) : x_(x), y_(y) { } // ctor
...
Point(const Point& copyme) = delete; // declare cctor and "=" as
Point& operator=(const Point& rhs) = delete; // as deleted (C++11)
private:
...
};
Point w; // compiler error (no default constructor)
Point x(1, 2); // OK!
Point y = w; // compiler error (no copy constructor)
y = x; // compiler error (no assignment operator)
If before C++11:
- In pre-C++11 code the copy constructor and assignment were often disabled by making them private and not implementing them.
Example:
class Point {
public:
Point(const int x, const int y) : x_(x), y_(y) { } // ctor
...
private:
Point(const Point& copyme); // disable cctor (no def.)
Point& operator=(const Point& rhs); // disable "=" (no def.)
...
};
Point w; // compiler error (no default constructor)
Point x(1, 2); // OK!
Point y = w; // compiler error (no copy constructor)
y = x; // compiler error (no assignment operator)
struct vs. class
In C, a struct can only contain data fields
- Has no methods and all fields are always accessible
- In struct foo, the foo is a “struct tag”, not an ordinary data type
In C++, struct and class are (nearly) the same!
- Both define a new type (the struct or class name)
- Both can have methods and member visibility (public/private/protected)
- Only real (minor) difference: members are default
public
in a struct and defaultprivate
in a class
Common style/usage convention:
- Use struct for simple bundles of data
- Convenience constructors can make sense though
- Use class for abstractions with data + functions
Access Control
Access modifiers for members:
- public: accessible to all parts of the program
- private: accessible to the member functions of the class
- Private to class, not object instances
- protected: accessible to member functions of the class and any derived classes (subclasses – more to come, later)
Reminders:
- Access modifiers apply to all members that follow until another access modifier is reached
- If no access modifier is specified, struct members default to public and class members default to private
Nonmember Functions
“Nonmember functions” are just normal functions that happen to use some class
- Called like a regular function instead of as a member of a class
object instance
- This gets a little weird when we talk about operators…
- These do not have access to the class’ private members
- can overload operators using both member functions and nonmember functions
friend Nonmember Functions
A class can give a nonmember function (or class) access to its nonpublic members by declaring it as a friend within its definition
friend
function is not a class member, but has access privileges as if it werefriend
functions are usually unnecessary if your class includes appropriate “getter” public functions
Complex.h:
class Complex {
...
friend std::istream& operator>>(std::istream& in, Complex& a);
...
}; // class Complex
Complex.cc:
std::istream& operator>>(std::istream& in, Complex& a) {
...
}
Namespaces
- Each namespace is a separate scope
- Useful for avoiding symbol collisions
- Namespace definition
- Creates a new namespace name if it did not exist, otherwise adds to the existing namespace, components (classes, functions, etc.) of a namespace can be defined in multiple source files
Classes vs. Namespaces
- classes are not namespaces.
- There are no instances/objects of a namespace; a namespace is just a group of logically-related things (classes, functions, etc.)
- To access a member of a namespace, you must use the fully
qualified name (i.e. nsp_name::member)
- Unless you are using that namespace
- You only used the fully qualified name of a class member when you are defining it outside of the scope of the class definition
Using the Heap
C++11 nullptr
- NULL as a pointer value that references nothing
C++11 introduced a new literal for this: nullptr
- New reserved word
- Interchangeable with
NULL
for all practical purposes, but it has typeT*
for any/everyT
, and is not an integer value- Avoids funny edge cases (see C++ references for details)
- Still can convert to/from integer 0 for tests, assignment, etc.
- Advice: prefer nullptr in C++11 code
- Though NULL will also be around for a long, long time
new / delete
To allocate on the heap using C++, you use the new
keyword instead of malloc()
from stdlib.h
- You can use new to allocate an object (e.g. new Point)
- Will execute appropriate constructor as part of object allocate/create
- You can use new to allocate a primitive type (e.g. new int)
To deallocate a heap-allocated object or primitive, use the delete
keyword instead of free()
from stdlib.h
Don’t mix and match!
- Never
free()
something allocated withnew
- Never
delete
something allocated withmalloc()
- Careful if you’re using a legacy C code library or module in C++
Example:
int* AllocateInt(int x) {
int* heapy_int = new int;
*heapy_int = x;
return heapy_int;
}
Point* AllocatePoint(int x, int y) {
Point* heapy_pt = new Point(x,y);
return heapy_pt;
}
heappoint.cc:
#include "Point.h"
using namespace std;
... // definitions of AllocateInt() and AllocatePoint()
int main() {
Point* x = AllocatePoint(1, 2);
int* y = AllocateInt(3);
cout << "x's x_ coord: " << x->get_x() << endl;
cout << "y: " << y << ", *y: " << *y << endl;
delete x;
delete y;
return 0;
}
Dynamically Allocated Arrays
To dynamically allocate an array:
type* name = new type[size];
To dynamically deallocate an array:
delete[] name;
It is an incorrect to use
delete name;
on an array
- The compiler probably won’t catch this, though (!) because it can’t
always tell if
name*
was allocated withnew type[size]
ornew type
; – Especially inside a function where a pointer parameter could point to a single item or an array and there’s no way to tell. - Result of wrong delete is
undefined
behavior
Arrays Example (primitive):
#include "Point.h"
using namespace std;
int main() {
int stack_int;
int* heap_int = new int;
int* heap_init_int = new int(12);
int stack_arr[10];
int* heap_arr = new int[10];
int* heap_init_arr = new int[10](); // uncommon usage
int* heap_init_error = new int[10](12); // bad syntax
...
delete heap_int; // ok
delete heap_init_int; // ok
delete heap_arr; // error - must be delete[]
delete[] heap_init_arr; // ok
return 0;
}
Arrays Example (class objects):
#include "Point.h"
using namespace std;
int main() {
...
Point stack_point(1, 2);
Point* heap_point = new Point(1, 2);
Point* err_pt_arr = new Point[10];// bug-no Point() ctr
Point* err2_pt_arr = new Point[10](1,2); // bad syntax
...
delete heap_point;
...
return 0;
}
malloc vs. new
malloc() | new | |
---|---|---|
What is it? | a function | an operator or keyword |
How often used(in C)? | often | never |
How often used(in C++)? | rarely | often |
Allocated memory for | anything | arrays, structs, objects, primitives |
Returns | a void* (should be cast) | appropriate pointer type(doesn’t need a cast) |
When out of memory | returns NULL | throws an exception |
Deallocating | free() | delete or delete[] |
Str Class Walkthrough
#include <iostream>
using namespace std;
class Str {
public:
Str(); // default ctor
Str(const char* s); // c-string ctor
Str(const Str& s); // copy ctor
~Str(); // dtor
int length() const; // return length of string
char* c_str() const; // return a copy of st_
void append(const Str& s);
Str& operator=(const Str& s); // string assignment
friend std::ostream& operator<<(std::ostream& out, const Str& s);
private:
char* st_; // c-string on heap (terminated by '\0')
}; // class Str
See:
- Str.h
- Str.cc
- strtest.cc
Lecture 14 - C++ Templates
What we’d prefer to do is write ‘generic code’
- Code that is type-independent
- Code that is compile-type polymorphic across types
C++ has the notion of templates: A function or class that accepts a type as a parameter
- You define the function or class once in a type-agnostic way
- When you invoke the function or instantiate the class, you specify (one or more) types or values as arguments to it
At compile-time, the compiler will generate the “specialized” code from your template using the types you provided
- Your template definition is NOT runnable code
- Code is only generated if you use your template
- Code is specialized for the specific types of data used in the template instance (e.g.: code for < on ints differs from code for < on strings)
Example
#include <iostream>
#include <string>
// returns 0 if equal, 1 if value1 is bigger, -1 otherwise
template <typename T> // <...> can also be written <class T>
int compare(const T &value1, const T &value2) {
if (value1 < value2) return -1;
if (value2 < value1) return 1;
return 0;
}
int main(int argc, char **argv) {
std::string h("hello"), w("world");
std::cout << compare<int>(10, 20) << std::endl;
std::cout << compare<std::string>(h, w) << std::endl;
std::cout << compare<double>(50.5, 50.6) << std::endl;
return 0;
}
Letting the compiler infer the types:
int main(int argc, char **argv) {
std::string h("hello"), w("world");
std::cout << compare(10, 20) << std::endl; // ok
std::cout << compare(h, w) << std::endl; // ok
std::cout << compare("Hello", "World") << std::endl; // hm…?
return 0;
}
Pair Class Definition
Pair.h:
#ifndef _PAIR_H_
#define _PAIR_H_
template <typename Thing> class Pair {
public:
Pair() { };
Thing get_first() const { return first_; }
Thing get_second() const { return second_; }
void set_first(Thing ©me);
void set_second(Thing ©me);
void Swap();
private:
Thing first_, second_;
};
#include "Pair.cc" // or (better?) put entire template def here
#endif // _PAIR_H_
Pair.cc:
template <typename Thing>
void Pair<Thing>::set_first(Thing ©me) {
first_ = copyme;
}
template <typename Thing>
void Pair<Thing>::set_second(Thing ©me) {
second_ = copyme;
}
template <typename Thing>
void Pair<Thing>::Swap() {
Thing tmp = first_;
first_ = second_;
second_ = tmp;
}
template <typename T>
std::ostream &operator<<(std::ostream &out, const Pair<T>& p) {
return out << "Pair(" << p.get_first() << ", "
<< p.get_second() << ")";
}
Know C++ more…..