Show Lecture.Constructors as a slide show.
The compiler also provides a move ctor, a move assignment operator, and a dtor, but we’re not ready to talk about those.
class Complex { public: Complex() { real = 0.0; imag = 0.0; } private: double real, imag; };
class Complex { public: Complex(const &Complex rhs) { real = rhs.real; imag = rhs.imag; } private: double real, imag; };
const
reference.
class Complex { public: Complex &operator=(const &Complex rhs) { real = rhs.real; imag = rhs.imag; return *this; } private: double real, imag; };
operator =
, with a space or not.
a=b=c
works.
const
reference.
default
and delete
class Foo { public: Foo() = default; }; class Bar { public: Bar() = delete; };
If you like the default methods, say so, so that the poor sap reading your code in the future doesn’t have to guess whether you like the default, or just forgot about it. Similarly, if you wish to forbid a method, say so.
Same for other default ctors, assignment operator, and dtor.
class Name { string first, last; public: Name() { first="John"; last="Doe"; } Name(string f) { first=f; last="Doe"; } Name(string f, string l) { first=f; last=l; } string full() const { return first + " " + last; } }; Name a, b("Beyoncé"), c("Barack", "Obama"); cout << a.full() << '\n' << b.full() << '\n' << c.full() << '\n';
John Doe Beyoncé Doe Barack Obama
b.first
gets initialized to ""
, and then assigned "Beyoncé"
.
Member initialization:
class Name { string first, last; public: Name() : first("John"), last("Doe") { } Name(string f) : first(f), last("Doe") { } Name(string f, string l) : first(f), last(l) { } string full() const { return first + " " + last; } }; Name a, b("Beyoncé"), c("Barack", "Obama"); cout << a.full() << '\n' << b.full() << '\n' << c.full() << '\n';
John Doe Beyoncé Doe Barack Obama
BAD!
class Name { string first, last; public: Name() { Name("John"); } // ☠ ☠ ☠ Name(string f) { Name(f, "Doe"); } // ☠ ☠ ☠ Name(string f, string l) : first(f), last(l) { } string full() const { return first + " " + last; } }; Name a, b("Beyoncé"), c("Barack", "Obama"); cout << a.full() << '\n' << b.full() << '\n' << c.full() << '\n';
Barack Obama
Good:
class Name { string first, last; public: Name() : Name("John") { } Name(string f) : Name(f, "Doe") {} Name(string f, string l) : first(f), last(l) { } string full() const { return first + " " + last; } }; Name a, b("Beyoncé"), c("Barack", "Obama"); cout << a.full() << '\n' << b.full() << '\n' << c.full() << '\n';
John Doe Beyoncé Doe Barack Obama
Holy smokes—it’s the same syntax as member initialization!
const
variable
Sometimes, this is the best technique:
class Name { string first = "John", last = "Doe"; public: Name() { } Name(string f) : first(f) {} Name(string f, string l) : first(f), last(l) { } string full() const { return first + " " + last; } }; Name a, b("Beyoncé"), c("Barack", "Obama"); cout << a.full() << '\n' << b.full() << '\n' << c.full() << '\n';
John Doe Beyoncé Doe Barack Obama
No assignments here—it’s all initialization.
b.first
does not start as "John"
and then get overwritten
to "Beyoncé"
. And we didn’t repeat ourselves.
% cat ~cs253/Example/Loud.h // A “Loud” class. It announces whenever its methods are called. #ifndef LOUD_H_INCLUDED #define LOUD_H_INCLUDED #include <iostream> class Loud { char c; void hi(const char *s) const { std::cout << "Loud::" << s; if (c) std::cout << " [c='" << c << "']"; std::cout << std::endl; // flush debug output } public: Loud(char ch = '\0') : c(ch) { hi("Loud()"); } ~Loud() { hi("~Loud()"); } Loud(const Loud &l) : c(l.c) { hi("Loud(const Loud &)"); } Loud(Loud &&l) : c(l.c) { hi("Loud(Loud &&)"); } Loud& operator=(const Loud &l) { c=l.c; hi("operator=(const Loud &)"); return *this; } Loud& operator=(Loud &&l) { c=l.c; hi("operator=(Loud &&)"); return *this; } Loud& operator=(char ch) { c = ch; hi("operator=(char)"); return *this; } Loud& operator++() { ++c; hi("operator++()"); return *this; } Loud operator++(int) { hi("operator++(int)"); const auto save = *this; ++*this; return save; } Loud operator+(const Loud &l) const { hi("operator+(const Loud &)"); return Loud(c+l.c); } }; #endif /* LOUD_H_INCLUDED */
#include "Loud.h" int main() { Loud a('x'); Loud b(a); Loud c=a; Loud d(); c = ++b; }
Loud::Loud() [c='x'] Loud::Loud(const Loud &) [c='x'] Loud::Loud(const Loud &) [c='x'] Loud::operator++() [c='y'] Loud::operator=(const Loud &) [c='y'] Loud::~Loud() [c='y'] Loud::~Loud() [c='y'] Loud::~Loud() [c='x']
Loud c=a
call the copy ctor instead of the assignment operator?
Loud
.
=
, but it’s a ctor.
explicit
, then only the ()
form
can be used. vector<int> v(3)
, not vector<int> v=3;
.
Loud d()
not do anything?
d
that takes no arguments, and returns a Loud
.
Loud
with no ctor arguments,
just do this: Loud d;
class CheapVec { public: CheapVec() : data(nullptr), count(0) { } CheapVec(int n) : data(new int[n]), count(n) { } ~CheapVec() { delete[] data; } private: int *data, count; }; int main() { CheapVec a, b(3), c=42; }
How did that final line compile‽
class CheapVec { public: CheapVec() : data(nullptr), count(0) { } CheapVec(int n) : data(new int[n]), count(n) { } ~CheapVec() { delete[] data; } private: int *data, count; }; int main() { CheapVec a; a = 42; }
free(): double free detected in tcache 2 SIGABRT: Aborted
class CheapVec { public: CheapVec() : data(nullptr), count(0) { } explicit CheapVec(int n) : data(new int[n]), count(n) { } ~CheapVec() { delete[] data; } int size() const { return count; } private: int *data, count; }; int main() { CheapVec a, b(3), c=42; }
c.cc:12: error: conversion from 'int' to non-scalar type 'CheapVec' requested
We can also have explicit
conversion methods.