Object-Oriented Programming
int main() {
std::vector<int> v11; // Calls 0-argument constructor. Creates empty vector.
// There's no difference between these:
// T variable = T{arg1, arg2, ...}
// T variable{arg1, arg2, ...}
std::vector<int> v12{}; // No different to first
std::vector<int> v13 = std::vector<int>(); // No different to the first
std::vector<int> v14 = std::vector<int>{}; // No different to the first
std::vector<int> v3{v2.begin(), v2.end()}; // constructed with an iterator
std::vector<int> v4{v3}; // Constructed off another vector
std::vector<int> v51{5, 2}; // Initialiser-list constructor {5, 2}
std::vector<int> v52(5, 2); // Count + value constructor (5 * 2 => {2, 2, 2, 2, 2})
}
int main() {
int n; // not constructed (memory contains previous value)
int n2{}; // Default constructor (memory contains 0)
int n3{5};
// This version is nice because it gives us an error.
int n4{5.5};
// You need to explictly tell it you want this.
int n6{static_cast<int>(5.5)};
// Not so nice. No error
int n5 = 5.5;
}
Can you think of a thing where you always have to remember to do something when you're done?
void ReadWords(const std::string& filename) {
std::ifstream f{filename};
std::vector<std::string> words;
std::copy(std::istream_iterator<std::string>{f}, {}, std::back_inserter{words});
f.close();
}
Resource acquisition is initialisation
A concept where we encapsulate resources inside objects
eg. Memory, locks, files
Every resource should be owned by either:
Another resource (eg. smart pointer, data member)
The stack
A nameless temporary variable
class MyClass {
~MyClass() noexcept;
};
MyClass::~MyClass() noexcept {
// Definition here
}
Advantages:
struct Node {
int data;
// Node is incomplete - this is invalid
// This would also make no sense. What is sizeof(Node)
Node next;
};
struct Node {
int data;
Node* next;
};
class Foo {
public:
// Members accessible by everyone
Foo();
protected:
// Members accessible by members, friends, and subclasses
// Will discuss this when we do advanced OOP in future weeks.
private:
// Accessible only by members and friends
void PrivateMemberFunction();
int private_data_member_;
public:
// May define multiple sections of the same name
};
// foo.h
class Foo {
public:
// Equiv to typedef int Age
using Age = int;
Foo();
Foo(std::istream& is);
~Foo();
void MemberFunction();
};
// foo.cpp
#include "foo.h"
Foo::Foo() {
}
Foo::Foo(std::istream& is) {
}
Foo::~Foo() {
}
void Foo::MemberFunction() {
Foo::Age age;
}
Are the following correct?
Sales_data a{"Harry Potter"};
Sales_data b{"Harry Potter"};
a.combine(b).print(std::cout);
a.print(std::cout).combine(b);
Are the following correct?
Sales_data a{"Harry Potter"};
Sales_data b{"Harry Potter"};
a.combine(b).print(std::cout);
a.print(std::cout).combine(b);
for each data member in declaration order
if it has an in-class initialiser
Initialise it using the in-class initialiser
else if it is of a built-in type (numeric, pointer, bool, char, etc.)
do nothing (leave it as whatever was in memory before)
else
Initialise it using its default constructor
class C {
int i{0}; // in-class initialiser
int j; // Untouched memory
A a;
// This stops default constructor
// from being synthesized.
B b;
};
class A {
int a_;
};
class B {
B(int b): b_{b} {}
int b_;
};
class NoDefault {
NoDefault(int i);
}
class B {
// Constructs s_ with value "Hello world"
B(int& i): s_{"Hello world"}, const_{5}, no_default{i}, ref_{i} {}
// Doesn't work - constructed in order of member declaration.
B(int& i): s_{"Hello world"}, const_{5}, ref_{i}, no_default{ref_} {}
B(int& i) {
// Constructs s_ with an empty string, then reassigns it to "Hello world"
// Extra work done (but may be optimised out).
s_ = "Hello world";
// Fails to compile
const_string_ = "Goodbye world";
ref_ = i;
// This is fine, but it can't construct it initially.
no_default_ = NoDefault{1};
}
std::string s_;
// All of these will break compilation if you attempt to put them in the body.
const int const_;
NoDefault no_default_;
int& ref_;
};
// For use with a database
class User {
static std::string table_name;
static std::optional<User> query(const std::string& username);
void commit();
std::string username;
}
User user = *User::query("Alice");
user.username = "Bob"
User::commit(); // fails to compile (commit is not static)
user.commit();
std::cout << User::table_name;
std::cout << User::username; // Fails to compile
class Age {
Age(int age);
};
// Explicitly calling the constructor
Age age{20};
// Attempts to use an integer
// where an age is expected.
// Implicit conversion done.
// This seems reasonable.
Age age = 20;
class IntVec {
// This one allows the implicit conversion
IntVec(int length): vec_(length, 0);
This one disallows it.
explicit IntVec(int length): vec_(length, 0);
std::vector<int> vec_;
};
// Explictly calling the constructor.
IntVec container{20};
// Implicit conversion.
// Probably not what we want.
IntVec container = 20;
class T {
T(const T&);
};
class T {
// A copy-assignment operator
T& operator=(const T& original);
// The copy-and-swap idiom
// This is also a copy-assignment operator
T& operator=(T copy) {
std::swap(*this, copy);
return *this;
}
};
MyClass base;
MyClass copy_constructed = base;
MyClass copy_assigned;
copy_assigned = base;
void f(MyClass&& x);
void inner(int&& value) {
++value;
std::cout << value << '\n';
}
void outer(int&& value) {
inner(value); // This fails? Why?
std::cout << value << '\n';
}
int main() {
f1(1); // This works fine.
int i;
f2(i); // This fails because i is an lvalue.
}
// Looks something like this.
T&& move(T& value) {
return static_cast<T&&>(value);
}
void inner(int&& value) {
++value;
std::cout << value << '\n';
}
void outer(int&& value) {
inner(std::move(value));
// Value is now in a valid but unspecified state.
// Although this isn't a compiler error, this is bad code.
// Don't access variables that were moved from, except to reconstruct them.
std::cout << value << '\n';
}
int main() {
f1(1); // This works fine.
int i;
f2(std::move(i));
}
class T {
T(T&&) noexcept;
};
class T {
T& operator=(T&&) noexcept;
};
To create safe object lifetimes in C++, we always attach the lifetime of one object to that of something else
class MyClass {
MyClass();
MyClass(int);
MyClass(const MyClass&);
MyClass(MyClass&&);
int GetValue();
};
// calls default constructor
std::optional<MyClass> opt1 = std::make_optional<MyClass>();
// calls int constructor
std::optional<MyClass> opt2 = std::make_optional<MyClass>(5);
// calls copy constructor
std::optional<MyClass> opt3 = *opt1;
// calls move constructor
std::optional<MyClass> opt4 = std::move(*opt1);
opt4->GetValue();
// Similar for make_unique and make_shared, but have to manually move / copy values
std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>();
std::shared_ptr<MyClass> sp2 = sp1;
std::shared_ptr<MyClass> sp3 = std::move(sp1);
MyClass* p2 = sp1.get();
std::unique_ptr<MyClass> up1 = std::make_unique<MyClass>(*sp1);
std::unique_ptr<MyClass> up2 = *up1; // But have to manually move / copy values like above
std::unique_ptr<MyClass> up3 = up1; // Fails (no copy constructor)
std::unique_ptr<MyClass> up4 = std::move(up1);
MyClass* p1 = up1.get();
up1->GetValue();
// path/to/file.h
namespace path {
namespace to {
class MyClass {
}
void MyFn();
} // namespace to
} // namespace path
// main.cpp
#include "path/to/file.h"
int main() {
path::to::MyClass myClass;
path::to::MyFn();
}
// path/to/file.cpp
namespace path {
namespace to {
MyFn() {
}
} // namespace to
} // namespace path
// Far cleaner than typedef, and the
// arguments are the right way around!
int main() {
using intvec = std::vector<int>;
intvec vec;
C::iterator_t it;
}
class IteratorC {
}
class C {
using iterator_t = IteratorC;
}
#include "path/to/file.h"
int main() {
// Imports a single thing
// into the current scope.
using path::to::MyClass;
MyClass myClass;
}
Using declaration
Type alias
#include "myclass.h"
using std::begin;
using std::end;
using std::swap;
int main() {
std::vector<MyClass> vec{{}, {}};
swap(vec[0], vec[1]);
for (auto it = begin(vec); it != end(vec); ++it) {
std::cout << *it << '\n';
}
}
// myclass.h
namespace ns {
void MyClass {
}
void swap(MyClass&, MyClass&);
}
#include "myclass.h"
int main() {
ns::MyClass v1, v2;
std::swap(v1, v2); // Calls std::swap
{
// Import std::swap to the current scope
using std::swap;
int i, j;
swap(i, j); // calls std::swap
swap(v1, v2); // calls ns::swap
}
}