(The compiler also provides a move ctor, a move assignment operator, and a dtor, but we’re not ready to talk about those.)
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!
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
There are 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/Examples/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;