Jay Bazuzi
Jay Bazuzi

Reputation: 46546

How can I safely extract a function from gnarly C++ code?

Suppose I have a 5000-line-long, deeply nested function, and I want to extract a 1000-line block into a new function.

In Java and C# I can let ReSharper, IntelliJ, and Visual C# handle the analysis required to safely extract a method, no matter how long and gnarly the code is. I can be confident that they won't change the behavior of the code, even if it's too complex for me to understand with my small brain.

The available C++ tools are not able to give me the same level of confidence. CLion, ReSharper, and Visual Assist will all introduce behavior changes when they extract a method.

What are my options?

Upvotes: 3

Views: 1687

Answers (1)

Jay Bazuzi
Jay Bazuzi

Reputation: 46546

One option is to use this recipe, based on Tennent's Correspondence Principle. You may apply it to a whole block (surrounded by braces) or to an if, while, or for statement (which create their own scopes).

1. Introduce a lambda and call it

Surround the block in question with:

[&]() {
    // original code
}();

Compile the file. Possible errors:

  • not all control paths return a value. You have an early return. Back up and either Eliminate Early Return/Continue/Break or extract something different.

  • a break/continue statement may only be used within... You have a break/continue. Back up and either Eliminate Early Return/Continue/Break or extract something different.

Check the new lambda for any return statements. If there are any returns and it's obvious that all code paths return, then add a return statement on the next line after the lambda. If there are any returns and it's not obvious that all code paths return, then back up and either Eliminate Early Return/Continue/Break or try extracting something different.

2. Introduce Variable on the lambda

i.e.

[&]() {
    // ...
}();

becomes:

auto Applesauce = [&]() {
    // ...
};
Applesauce();

Compile to make sure you didn't typo.

3. Set the return type

Set the return type on the lambda (even if it's void). In Visual Studio, the tooltip over auto will tell you the type.

i.e.:

auto Applesauce = [&]() -> SOMETYPE {
    // ...
};

Compile to make sure you got the return type correct.

4. Capture explicitly

Replace [&] with [this] (or [] in a free function) and compile.

For each error about a variable that must be captured: - Copy the variable name - Paste it in to the capture list, prefixed with & - Repeat until green.

i.e.:

auto Applesauce = [this, &foo]() -> void {
    cout << foo;
};

The order of the capture list will influence the order of the parameters of the final function. If you want the parameters in a particular order, now is a good time to reorder the capture list.

5. Convert captures to parameters

For each captured local variable (except this) - Go to the definition of the variable - Copy the variable declaration (e.g. Column* pCol) - Paste in to the lambda parameter list - Make the parameter const and by-reference - Remove the variable from the capture list - Pass the variable in to the call - Compile.

i.e.:

Column* pCol = ...
auto Applesauce = [&pCol]() -> void { cout << pCol->name(); };
Applesauce();

becomes

Column* pCol = ...
auto Applesauce = [](Column*& pCol) -> void { cout << pCol->name(); };
Applesauce(pCol);

6. Convert lambda to function

If this is captured, use 6A. If this is not captured, use 6B.

6A. Convert this-bound lambda to member function

  • Cut the lambda statement and paste it outside the current function
  • Remove = [this]
  • Copy the signature line
  • Add SomeClass::
  • Paste the signature in to the class declaration in a private section.
  • Compile

i.e.:

auto SomeClass::Applesauce () const -> void {
    // ...
};

6B. Convert non-this Lambda to free function

  • Cut the lambda statement and paste it above the current function.
  • Remove = []
  • Compile

Upvotes: 6

Related Questions