根据的建议,我知道答案建议在声明变量时应该使用“auto”而不是实际类型。

然而,问题在于使用 auto 之前和之后代码的可读性,但我发现“auto”的最大问题不是可读性,而是“auto”阻碍我在变量声明中使用诸如“MyClass myclass = …”之类的关键字来搜索我需要修复的代码。

例如,假设我有一个 C++ 手机游戏项目,需要使用 DTO“UserSettings”加载用户设置,并且 UserSettings 可以从游戏服务器、移动设备加载,或者由用户在某些页面编辑,例如(忽略 .h 和类定义以获得更简单的代码):

旧用户登录页面.cpp

OldUserLoginPage::loadUserData(){
    //some code call http
    UserSettings userSettings=loadUserDataResponse.userSettings;
    //some other code
}

用户简历页面.cpp

UserResumePage::viewDidLoaded(){
    //some init code
    UserSettings userSettings=AppData::getUserSettings();
    //some other init code
}

设置页面.cpp

 SettingsPage::onLoadDefaultButtonPressed(){
    //some code
    UserSettings userSettings=DefaultUserSettings::getInstance();
    //some other code
}

当游戏运行几个月后,突然有用户报告说用户设置似乎保存失败,我确信我会忘记哪个 .cpp 包含 UserSettings,也会忘记 loadUserDataResponse.userSettings、AppData::getUserSettings() 和 DefaultUserSettings::getInstance(),但我知道我可以搜索关键字“UserSettings”来列出处理用户设置的所有代码部分。

但是,如果我只使用“auto”而不是 UserSettings,我就无法搜索关键字“UserSettings”来查找哪部分代码使用了 UserSettings。虽然使用 auto 后,我也可以单独浏览每个 .cpp 来找到相关代码,但搜索关键字“UserSettings”可以帮助我更快地找到并修复代码中的错误。此外,当新队友加入团队并开始维护代码时,队友可能无法立即知道哪个实际页面引用了 ???Page.cpp,但新队友可以搜索关键字“UserSettings”来查找项目中哪部分代码可能包含错误。

另外,即使在单个 .cpp 中,我发现变量声明中的类型也可以帮助我快速找到相关代码,例如,假设有一个关于购买物品和魔法石的页面:

商店页面.cpp

ShopPage::method1(){
   //some code
   this->updateTotalCost();
}

ShopPage::method2(){
   //some code
    this->updateTotalCost();
}
.
.
.
ShopPage::updateTotalCost(){
   //some other code
   double totalCost=item1.quanity*item1.price+item2.quanity*item2.price+...
}

另外,过了几天,假设有些用户发现成本似乎计算错误,我忘记了哪个函数和变量名处理成本计算,但我记得成本必须是“double”类型,那么我可以搜索“double”跳转到相关代码来调查问题。

虽然我同意“auto”可以帮助我更快地编写代码,但代码被阅读和维护的次数比被编写的次数要多,所以我宁愿在编写 C++ 时把“auto”当成不存在,这样我就可以通过特定类型的变量声明来搜索代码,以便快速找到并修复代码。所以我的问题是,上述原因是避免使用“auto”的理由吗?

1

  • 1
    auto(隐式类型)只是类型擦除的另一种形式——就像参数多态性(模板)、子类型、函数重载等一样。它与编写代码的速度无关,而是与抽象的使用和与依赖项的分离有关。反对的相同论点auto通常也针对抽象基类、子类型多态性、模板参数和重载函数名称的使用。(通常由开发人员工具和单元测试的使用覆盖。导航之类的事情通常由一个不错的 IDE 处理)。


    – 



最佳答案
3

不,我不认为这是避免使用 auto 的正当理由。某些情况下,这可能是一个正当的理由(尽管你的两个例子在我看来都有些争议)。

首先我要说的是,我部分不同意 Herb Sutter 对另一个问题的接受答案,即auto只要您不想要显式转换,就应该将其用作默认答案。当您阅读该答案的评论时,您会发现我并不是唯一持这种观点的人,因为这种策略会对可读性产生负面影响。

我使用的场景auto主要是

  • 类型名称会在同一行上不必要地重复

  • 确切的类型名称可能类似于较长的技术迭代器类型名称,这并不重要

在第一种情况下,使用auto不会降低“全局可搜索性”;在第二种情况下,您不太可能需要对类型名称进行全局搜索。

让我评论一下你的例子:

  • 您的第二个示例显示了一个情况,其中全局搜索类型名称毫无意义 –double就像 一样不具体auto。不过,我猜 Herb Sutter 也会同意使用显式类型名称 – 以防您想确保 totalCost 具有 类型double,而不管右侧使用什么类型。这就是 Herb 所说的“致力于特定类型”。

  • 在您的第一个示例中,我认为在搜索某个错误的根本原因时,从全局搜索开始并不是一个好策略UserSettings。当保存操作“失败”时,我会首先在保存操作本身中设置一个断点,并检查操作是否执行(并失败),或者是否根本没有执行。对于后一种情况,作为下一步,我会找出我期望从哪里调用保存操作,并在所有这些调用点中设置断点。一个好的 IDE 可能会向您显示所有潜在的调用者,如果没有这样的 IDE,全局搜索保存操作的名称会更有用。

    当然,当您的保存操作只是简单地命名为save,并且您的代码库中还有 30 个其他类时save,作为最后的手段,您可以进行全局搜索,UserSettings并希望返回的位置数小于代码库中包含该单词的位置数save。但正如您所见,全局搜索类名可能并不总是像乍一看那么有用。

这确实是正当的理由。

这是否足以成为auto 避免使用的原因– 取决于是否有更好的理由使用auto它。想必您可以在最初引入的设计文档中找到这样的理由;有一篇由 Stroustrup 等人撰写的论文,介绍了这一概念。

(就我个人而言,我非常支持类型系统来完成那些你原本需要在脑海中完成的工作。所以对我来说,进行类型推断的理想方式就是简单地写“auto”,然后 IDE 用它推断出的类型替换它——这将结合两者的优势。但显然没有足够多的人想到这一点来实现它。)

2

  • +1,这是理想的方式。


    – 

  • 1
    在编写代码时修复类型会抵消自动的主要优点之一:它是在编译时确定的,并且当推断类型改变时也会改变,而不是要求程序员更改每个实例。


    – 

这取决于您的开发软件有多聪明。搜索所有使用 MyClass 作为文本的情况很容易。但有些 IDE 还能够搜索 MyClass 作为符号的情况。这可以通过使用包含“MyClass”扩展的宏、在子类中使用“super”或使用任何产生 MyClass、MyClass& 等的“auto”来实现。

现在,如果您只想要一个 MyClass 实例,请随意使用“MyClass”而不是“auto”。如果您并不真正关心实际类型,则可以使用 auto。例如,如果函数返回迭代器,这通常是一种复杂的类型,而您并不真正关心类型,因此您可以使用 auto。