The common practice is to avoid lazy initialisation where at all possible.
If there is a lazy initialisation scheme, there is nothing preventing the caller (or user) of the object from doing something that relies on the object being initialised, before it is actually initialised. That can result in chaos.
To cope with that, the object (or class) implementation needs to keep track of whether an object is actually initialised or not. That makes the class implementation more complicated - if ANY member function forgets to check if the object is initialised, or if any member function places the object in an invalid state, chaos can ensue. If the object (or class) does not do that, the class is harder to use, because any mistake by code which uses the class causes problems.
Instead, the technique more usually used is that (1) constructors establish an invariant (2) member functions assume maintain that invariant and (3) destructors clean up.
In other words, the constructors initialise the object, member functions ensure the object stays in a sensible state. The member functions are allowed to ASSUME the object is in a valid state when they are called .... so don't need to check. As long as all member functions ensure the object is still in a valid state when they return, there is no problem.
The only exception is the destructor, which causes the object to cease to exist. In other words, after destroying an object (invoking its destructor) no members of that object should be used at all.
For the caller, it is easy - don't create an object until information needed to create it is available. In other words, instead of
SomeObject object;
// gather data needed to initialise object
// Danger, danger: it is possible to mistakenly use object as if it is initialised here
object.initialise(needed_data);
// do other things with object
//object ceases to exist (e.g. end of {} block).
do this
// gather data needed to initialise object
// note that the compiler will reject using object here
SomeObject object(needed_data);
// do other things with object
//object ceases to exist (e.g. end of {} block).
In C++, there is nothing preventing an object being created when it is needed. Variables are not limited to being declared at the top of a block or anything like that.