If you are new to C++, it may be desirable to shy away from doing big work in a constructor. Constructors are wedded tightly to the way the language behaves, and you really don't want the constructor getting called unexpectedly (
explicit is your friend!). Constructors are also rather unique in that they cannot return a value. This can make error handling more complicated.
This sort of recommendation becomes less important as you learn more about C++ and all the different things which can cause constructors to get called. In the perfect world, the correct answer is that your library should do exactly what the user wanted it to at all times. Quite often in C++ that is best implemented by doing things inside constructors. However, you just want to make sure that you understand constructors enough not to trap your users in a corner. Debugging why the program operates poorly because a constructor got invoked when a user did not intend it to be invoked can be a pain.
Likewise exception handling in constructors can be more difficult because the scope where the exception occurs is entwined with the scope where a variable gets created, rather than where you want to use it. Exceptions can be infuriating when they occur before
main() is run, denying you the ability to handle that exception for the user. This can happen if you use global variables and the errors can be cryptic.
One option that sits between the extremes is to use factory methods. If your users expect to use factory methods, it makes sense that they could do a little extra initialization at that point. A factory method won't be called by accident like a constructor might, so it's harder to mess it up.
Once you are confident you understand the consequences of doing work in a constructor, it can be a powerful tool. The ultimate example of doing work in a constructor might be the
lock_guard class. Lock guards are constructed by passing them a lock such as a mutex. They call
.lock() on it during their constructor (aka doing real work) and
.unlock() on it during their destructor.
These classes get away with doing real work in the constructor because they are designed to be used that way. Anyone working with a
lock_guard is well aware that the constructor does work because code that uses it looks something like:
lock_guard guardMe(myLock); // locks myLock
// as I leave this block, guardMe will unlock myLock. It will do so
// even if I leave via an exception!
If you look at the implementations of
lock_guard classes, you will find easily 80% of their attention or more is focused on how to manage the constructors correctly to prevent surprises. Properly implemented, they're very powerful. Improperly implemented
lock_guards can be the bane of your existence because they can appear to do one thing when they actually do another.