Skip to content

Avoid creation of objects that are not in a usable state

Objects that can be created in an unusable state are error prone. A common anti-pattern is a class design with an initialize(...) function. This suggests that the constructor does not sufficiently construct and initialize the class on construction.

class Connection
{
public:
Connection();
void initialize(/*...*/);
void send(/*...*/); // precondition: connection must be initialized
};

The need for the user to call initialize() before using this class is error-prone.

void example_incorrect_use()
{
auto con = Connection{};
con.use() // incorrect: forgot to initialize
}
void example_correct_use()
{
auto con = Connection{};
con.initialize(/*...*/);
con.use() // OK
}

Instead of relying on the user to do the correct thing, try to design your class API to make it easier to use right, and hard use the wrong.

Use a constructor to create a usable object

Section titled “Use a constructor to create a usable object”

By default, encapsulate initialization logic within the constructor: C.41: A constructor should create a fully initialized object, so that it either constructs a usable object, or it raises an exception on failure.

If the constructor is not convenient, then delegate the initialization of the usable state to a factory function or builder pattern to construct-and-initialize. For example, when:

  • Construction may require complex logic (e.g., error-prone configuration parsing, network/database connections).
  • You might want to return a std::optional<T> or a std::unique_ptr<T> or std::expected to indicate possible failure without exceptions.

Make the constructor private to disallow users from creating an object without using the factory function:

class Connection
{
public:
static std::optional<Connection> create(/*...*/)
{
/* ... */
}
private:
Connection();
};

Separate the different possibles states into different classes, for example:

class EstablishedConnection {
public:
void send(/*...*/);
private:
/*...*/
};
class PendingConnection {
bool ready = false;
public:
/*...*/
};
class Connection {
public:
static PendingConnection create() {
return PendingConnection();
}
void usage()
{
auto pending = Connection::create(/*...*/);
// ...
if (pending.is_ready()) {
auto established = pending.establish();
established.send(/*...*/);
}
}

Using separate classes (PendingConnection, EstablishedConnection) makes the object’s state explicit, preventing accidental misuse (e.g., calling send() before the connection is ready). The approach guides users to follow the correct sequence, making misuse less likely and code more readable and maintainable.

If you see comments describing preconditions on methods, like:

  • // Must call init() first
  • // Only call if foo is not null It’s a sign that the API allows misuse. If your function needs precondition comments, consider refactoring your API to make it safer and easier to use.