4

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 just delete t, triggering the empty constructor, so this is equivalent to delete t but prevents stack allocation)
  • Initialization can be multi-stage, where build_T_1() must be called before build_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?

Arno
  • 151

1 Answers1

6

It may sound trivial, but the approach which will probably serve you most is quite generic:

  1. Identify a class you want to refactor next.

  2. Make sure you have enough regression tests in place for this class.

  3. Rewrite the memory management for that class

  4. Run the tests, fix the bugs.

  5. 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".

Doc Brown
  • 218,378