Edit: @Ben Cottrell's comment said this was similar to a question about spaghetti code. While both questions involve large codebases, mine addresses a specific technical pattern: manual memory management vs RAII, with concrete examples of the current structure. The other question asks for general legacy code strategies, and its answers (VCS, testing) don't address my specific object lifecycle refactoring challenge.
I've inherited a large codebase (around 200k lines) and I'm trying to understand the best way to modernize its object lifecycle management. The code is actually well-organized in many ways:
The project is split into many classes (100+) with good separation of concerns. However, it has an unusual pattern for object lifecycle management that relies heavily on manual memory management with raw pointers.
The current pattern looks like this:
Each class T follows a specific structure:
- Headers (T.h) contain class declarations with basic constructors/destructors and accessors
- Implementation files (T.cpp) have mostly empty constructors/destructors
- Separate files (T_func.h/cpp) contain the actual initialization logic in standalone functions
The initialization happens through functions like T_func::build_T(T&, ...)
rather than in constructors. Similarly, cleanup occurs through T_func::clear(T*)
functions instead of destructors.
There are some interesting constraints:
- Some destructors are protected, forcing you to use the
clear(t)
functions (sometimes those justdelete t
, triggering the empty constructor, so this is equivalent todelete t
but prevents stack allocation) - Initialization can be multi-stage, where
build_T_1()
must be called beforebuild_T_2()
- These initialization stages often work with hierarchical object structures
- The stages can span multiple modules
The getters and setters are basically just direct access to member variables.
I'd like to refactor this to use modern C++ patterns - specifically RAII where objects manage their own lifecycle and can be stack-allocated. Any suggestions on the best approach?
1 Answer 1
It may sound trivial, but the approach which will probably serve you most is quite generic:
Identify a class you want to refactor next.
Make sure you have enough regression tests in place for this class.
Rewrite the memory management for that class
Run the tests, fix the bugs.
In case you are satisfied, go to step 1.
Of course, it may happen that your slice of work was too large, and you don't get the code base stable within a reasonable amount of time (for me, I avoid refactoring cycles longer than one day, but YMMV). If you run into such a case, undo your latest changes and try to find a smaller slice.
I would also consider to focus the refactoring on parts of the code base you expect to have to touch for new business features next, or where you know there is a hidden bug. Don't mess around with working code when you don't have no other reason to look into than "the memory management is old-fashioned".
Explore related questions
See similar questions with these tags.
clear()
as a custom deleter. The hardest part will be multi-stage initialization. Sometimes this has to be kept, sometimes a Builder or Typestate can help make this more maintainable.