what the compiler generates for you

If a class declares none of them, the compiler implicitly generates four member functions: default constructor, copy constructor, copy assignment operator, and destructor. The moment you declare any constructor yourself, the implicit default constructor goes away — you have to write one if you still want Animal a; to compile. Declaring a destructor also suppresses the implicit move constructor/assignment (see Move Semantics for the rule of five this leads to).

initialization lists

Members listed in the initialization list are constructed directly with the given value. Members assigned in the constructor body are first default-constructed, then overwritten — strictly more work, and the only option at all for members that are references, const, or lack a default constructor. There's also a shadowing trap worth knowing about explicitly:

class Point {
  int x, y, z;
public:
  Point(int x, int y, int z) : x(x) {   // only x is initialized via the list
    y = y;          // BUG: assigns the parameter y to itself — the member y is never touched
    this->z = z;     // correct — this-> disambiguates the member from the parameter
  }
};
            
When a constructor parameter shares a name with a member (a very common style, as above), plain y = y inside the body resolves to the parameter on both sides — the member is left uninitialized. Either put every parameter-named member in the initialization list, or use this-> explicitly in the body.

the full set: default, parameterized, copy, copy assignment, destructor

A class that owns a resource (here, two std::string members, standing in for anything that needs real copy semantics) written out with all three rule-of-three members plus the two rule-of-five move members:

class Animal {
  std::string name, sound;
public:
  Animal() : name("unknown"), sound("unknown") {}                       // default ctor
  Animal(std::string name, std::string sound) : name{name}, sound{sound} {}  // parameterized ctor

  Animal(const Animal &rhs) : name(rhs.name), sound(rhs.sound) {}     // copy ctor
  Animal & operator=(const Animal &rhs) {                          // copy assignment
    if (this != &rhs) { name = rhs.name; sound = rhs.sound; }
    return *this;
  }

  Animal(Animal &&rhs) noexcept                                     // move ctor
    : name(std::move(rhs.name)), sound(std::move(rhs.sound)) {}
  Animal & operator=(Animal &&rhs) noexcept {                       // move assignment
    if (this != &rhs) { name = std::move(rhs.name); sound = std::move(rhs.sound); }
    return *this;
  }

  ~Animal() = default;                                                    // dtor
};
            
The copy assignment's if (this != &rhs) self-assignment check matters: without it, a = a would (for a class managing a raw resource) potentially free the resource before copying from it — copying from memory that was just invalidated. For members that are themselves well-behaved types like std::string it's mostly a wasted check rather than a correctness bug, but the habit is worth keeping once a class manages a raw pointer.

implicit vs. explicit construction

A constructor callable with exactly one argument doubles as an implicit conversion, whether you intended that or not. Marking it explicit turns that conversion off, requiring the caller to construct the object on purpose.

class Meters {
  int value;
public:
  Meters(int value) : value(value) {}      // one-argument ctor — an implicit conversion path
};

void report(const Meters &m) { /* ... */ }

Meters m = 5;         // implicit int -> Meters conversion — probably not what you meant
report(42);            // also compiles — 42 silently becomes a Meters(42)
            

class Meters {
  int value;
public:
  explicit Meters(int value) : value(value) {}   // no more silent conversion
};

Meters m = 5;          // error: no implicit conversion from int to Meters
Meters m(5);            // fine — explicit construction
report(Meters(42));      // fine — the caller has to say what they mean
            
Default to explicit on any single-argument constructor unless you specifically want the implicit conversion — it's easy to add later if needed, and hard to track down bugs caused by a conversion nobody meant to invoke.

const member functions and mutable

Covered in depth on C++ Fundamentals (const correctness) — the class-design version of the rule: a const object can only call const member functions, so any method that doesn't need to mutate observable state should be marked const. That maximizes how many contexts the class works in (e.g. being usable through a const& parameter) and lets the compiler flag accidental mutation as a build error instead of a runtime bug.

put together: the shadowing fix and the rule of three, traced

A small BankAccount class that avoids the initialization-list shadowing bug with an explicit this->, and prints a message from every rule-of-three member so you can see exactly which one runs for each line in main(): TopNotchNote/cpp/classes_constructors_bankaccount_rule_of_three.cpp

private constructors and destructors

A private constructor blocks direct instantiation — useful for a class that should only ever be created through a factory function, or that should never be instantiated at all (see the Singleton pattern in Design Patterns). A private destructor is a stronger restriction: it blocks stack allocation entirely, because a stack object is destructed automatically at the end of its scope, which requires an accessible destructor. Only heap allocation through new remains possible — and even then, nothing can call delete on it from outside the class, so cleanup has to be handled by a member function.

class HeapOnly {
  ~HeapOnly() {}     // private dtor — stack objects and inheritance are both blocked
public:
  HeapOnly() = default;
  void destroySelf() { delete this; }   // the only way this object can be destroyed
};

HeapOnly a;                 // error: destructor is private, can't be stack-allocated
HeapOnly *p = new HeapOnly(); // fine — heap allocation doesn't need the destructor at construction time

the "three issues" with a class that has no constructor of its own

These come up together often enough to be worth listing as a unit: if a class ends up with no usable default constructor (for instance because you declared other constructors but not a default one), you can't create an instance with no arguments, you can't instantiate a C-style array of it (new T[10] default-constructs each element), and if the base class in an inheritance hierarchy has no default constructor, the derived class doesn't get an implicit default constructor either — the derived constructor would have no way to initialize the base subobject.

where to go from here

Move Semantics — the rule of five this page's rule of three leads into.
Inheritance & Polymorphism — what happens to constructors and destructors under inheritance.
Operator Overloading — giving a class natural-looking +, ==, and friends.

reference

cppreference — constructors
cppreference — explicit specifier