A default constructor initializes an object without caller-supplied arguments. C++ may supply one when no constructor is written, but supplying another constructor prevents automatic generation. Treat the generated constructor as a convenience, not proof that the resulting object has meaningful state. section 2.4 section 7.3
Use a default constructor deliberately:
- Use `= default` when the compiler-generated behavior is correct and making that intent explicit improves readability. section 2.4
- Use `= delete` when an operation such as assignment must not be available, rather than hiding an undeclared member privately. section 2.4
- Do not use a default constructor merely to create an object that must later receive essential data through setters. section 7.8 section 10.2
- Make construction establish the class invariant: initialize every instance variable meaningfully, reject invalid input, or prevent construction. section 7.3 section 10.2
From hidden operations to explicit intent
A `Container` can support default construction and copying while rejecting assignment. The modern declaration communicates both decisions directly: `Container() = default;` and `Container& operator=(const Container&) = delete;`. This is clearer than writing an empty default constructor and hiding assignment privately. section 2.4
Listing 2.8 A simple class with defaulted and deleted members
class Container {
private:
std::vector<int> values;
public:
Container() = default; #1
Container(const Container& o) : values(o.values) {}
Container& operator=(const Container& o) = delete; #2
void add(int n) { values.push_back(n); }
};
int main() {
Container c1;
c1.add(42);
Container c2(c1);
// c2 = c1; #3
return 0;
}
Compare explicit defaulting and deletion with the older handwritten-and-hidden-member approach. section 2.4
A default object can be invalid or meaningless
A `Person p1;` created without a programmer-supplied constructor may contain an empty `name` and an undefined built-in `age`. Requiring a name and validated age during construction prevents callers from using a partially initialized object. If construction cannot establish valid state, throw an exception instead of relying on a later validity check. section 7.8 section 10.2
Listing 10.2 Throwing exceptions for construction failures
class Person {
private:
std::string name;
int age;
static const std::string& validateName(const std::string& name) { #1
if (name.empty())
throw std::invalid_argument("name must not be empty");
return name;
}
static int validateAge(int age) { #2
if (age < 0)
throw std::out_of_range("age must be non-negative");
return age;
}
public:
Person(const std::string& name, int age) :
name(validateName(name)), age(validateAge(age)) {} #3
int getAge() const { return age; }
const std::string& getName() const { return name; }
bool isValid() const { return age > -1; }
};
int main() {
// Person sally("Sally"); #4
Person sally("Sally", 27);
std::cout << sally.getName() << " is " << sally.getAge()
<< " years old\n";
Person brian("Brian", -1); #5
std::cout << brian.getName() << " is " << brian.getAge()
<< " years old\n";
return 0;
}
Shows constructor validation that rejects an empty name or negative age before the object becomes usable. section 10.2


