我该如何安全地从复杂的 C++ 代码中提取一个函数?

3
假设我有一个5000行的深度嵌套的函数,我想将其中的1000行内容提取成一个新的函数。
在Java和C#中,我可以使用ReSharper、IntelliJ和Visual C#处理所需的分析,安全地提取一个方法,无论代码多么长或复杂。我相信它们不会改变代码的行为,即使对于我这个小脑袋来说,它太过复杂而难以理解。
然而,现有的C++工具不能给我同样的信心。CLion、ReSharper和Visual Assist在提取方法时都会引入行为变化。
那么我的选择有哪些呢?

2
如果您包含您正在尝试“提取”的“棘手的C ++”代码示例,那么这可能会更有趣。 - user2100815
@NeilButterworth 添加了一句话关于那个。 - Jay Bazuzi
1个回答

6

一种选择是使用基于Tennent的对应原理的这个配方。您可以将其应用于整个代码块(用大括号括起来)或ifwhilefor语句(它们会创建自己的作用域)。

1. 引入lambda并调用它

将需要操作的代码块用如下方式包围:

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

编译该文件。 可能的错误:

  • 并非所有控制路径都返回值。 你有一个早期返回。回退并消除早期返回/Continue/Break或提取其他内容。

  • break/continue语句只能在... 你有一个break/continue。回退并消除早期返回/Continue/Break或提取其他内容。

检查新的lambda是否有返回语句。 如果有任何返回且很明显所有代码路径都会返回,则在lambda之后的下一行添加返回语句。如果有任何返回且不明显所有代码路径都将返回,则回退并消除早期返回/Continue/Break或尝试提取其他内容。

2. 引入变量到lambda中

例如:

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

变成:

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

编译以确保您没有输错。

3. 设置返回类型

在lambda上设置返回类型(即使是void)。在Visual Studio中,将鼠标悬停在auto上将显示类型。

例如:

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

编译以确保您正确获取了返回类型。

4. 明确捕获

[&]替换为[this](或在自由函数中使用[])并进行编译。

对于每个关于必须捕获的变量的错误: - 复制变量名称 - 将其粘贴到捕获列表中,前缀为& - 重复此过程直至无误。

例如:

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

捕获列表的顺序将影响最终函数参数的顺序。如果你希望参数按特定顺序排列,现在是重新排序捕获列表的好时机。

5. 将捕获转换为参数

对于每个被捕获的本地变量(除了this) - 转到变量的定义 - 复制变量声明(例如Column* pCol) - 粘贴到lambda参数列表中 - 使参数成为const和按引用传递 - 从捕获列表中删除变量 - 将变量传入调用 - 编译.

即:

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

变成

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

6. 将lambda表达式转换为函数

如果捕获了this,请使用6A。 如果未捕获this,请使用6B。

6A. 将绑定了this的lambda表达式转换为成员函数

  • 剪切lambda语句并将其粘贴到当前函数之外
  • 删除= [this]
  • 复制签名行
  • 添加SomeClass::
  • 将签名粘贴到类声明的私有部分中。
  • 编译

例如:

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

6B. 将非this Lambda转换为自由函数

  • 剪切Lambda语句并将其粘贴到当前函数上方。
  • 删除= []
  • 编译

我认为问题标题应该改为“如何使用lambda重构代码”,因为lambda不仅可以帮助您“提取”自由和成员函数,而且可以帮助您在一般情况下重构代码。例如,当初始化需要多行计算时,我经常使用立即调用的lambda来初始化const局部变量;这种立即调用的lambda(IIL)并不总是成为函数,因为它们只被使用一次。此外,由于我使用IIL进行初始化,我还有机会使我的对象成为const(如果它们不需要是不可变的)。 - Nawaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接