重新定义/隐藏局部变量有多糟糕
How bad is redefining/shadowing a local variable?
在将遗留项目升级到VS2015时,我注意到有很多错误,例如在函数中重新定义了局部变量。
void fun()
{
int count = applesCount();
cout << "Apples cost= " << count * 1.25;
for (int bag=0; bag<5;bag++)
{
int count = orangesCount(bag);
cout << "Oranges cost = " << count * 0.75;
}
}
编译器发出的错误/警告消息为:
declaration of 'count' hides previous local declaration
我知道对变量count
使用相同的名称显然不是一个好的做法,但编译器真的会把事情搞砸吗?或者通常他们会很优雅地处理这种情况?
是否值得更改和修复变量名称,或者不太可能造成任何伤害,并且风险很低或没有风险?
我注意到有很多错误,比如在函数内部重新定义了局部变量。
您不是在演示重新定义。您显示了变量阴影的示例。
变量阴影在语法上不是错误。它是有效的,定义良好。然而,如果您的意图是从外部范围使用变量,那么您可以认为这是一个逻辑错误。
但是编译器真的会把搞砸吗
没有。
阴影的问题是程序员很难跟踪。这对编译器来说是微不足道的。你可以在这个网站上找到很多问题,这些问题源于隐藏变量引起的混乱。
在这个小函数中,不难找出哪个表达式使用了哪个变量,但可以想象这个函数是几十行和几个嵌套的连续块。如果函数足够长,以至于您无法一眼看到不同范围中的所有不同定义,那么您很可能会产生误解。
declaration of 'count' hides previous local declaration
这是一个非常有用的编译器警告。您还没有用完名称,为什么不为函数中的所有局部变量指定一个唯一的名称呢?但是,没有必要将此警告视为错误。这只是一个提高程序可读性的建议。
在这个特定的例子中,在内部作用域打开后,您不需要在外部作用域中使用count
,所以您还可以为两个计数重用一个变量。
更改和修复变量名值得吗
这取决于您是否更重视短期工作量而不是长期工作量。现在,将代码更改为使用唯一的、描述性的局部变量名是一项"额外"的工作,但每次有人以后必须理解程序时,不必要的阴影会增加心理挑战。
IMHO,错误的编码实践。难以维护和阅读。
编译器可以区分外部变量和内部变量。
有了好的词汇表(和词库),就不需要使用相同的变量名。
对变量进行阴影处理(这就是它)具有完全定义好的语义,因此编译器不会把它搞砸。它将完全按照指示行事,并产生明确的结果。
问题出在人类身上(通常都是这样)。阅读和修改代码时很容易出错。如果一个人不太小心,那么跟踪引用的是哪个具有给定名称的变量可能会很困难,而且当你认为你在修改一个变量,但实际上你在修改另一个变量时,很容易出错。
所以,编译器很好,程序员才是问题所在。
根据我的经验,编译器通常会非常优雅地处理这个问题。
然而,这无疑是一种糟糕的做法,除非您有真正令人信服的理由这样做,否则您应该重用旧变量(如果这样做在逻辑上有意义的话),或者声明一个[名称不同]的新变量。
可能非常糟糕:
考虑这个
std::vector<int> indices(100);
if (true) {
std::vector<int> indices(100);
std::iota(indices.begin(), indices.end(), 0);
}
// Now indices will be an all-0 vector instead of the an arithmetic progression!
在Visual Studio中,使用Warning Level4 /W4
进行编译时,即使在前缀中使用隐式this
指针来消除歧义,也会输出警告,如this->Count
:
警告C4458:"Count"的声明隐藏类成员
尽管编译器很少在值上犯错误,但阴影可能会被滥用,并随着时间的推移而变得混乱。
以下是应该避免的对Count成员变量进行阴影处理的示例:
来自虚幻编码标准文档。
class FSomeClass
{
public:
void Func(const int32 Count)
{
for (int32 Count = 0; Count != 10; ++Count)
{
// Use Count
}
}
private:
int32 Count;
}
处理此问题的一个选项是在出现阴影时为传入参数名称添加前缀,如下所示:
class FSomeClass
{
public:
void Func(const int32 InCount)
{
Count = InCount;
for (int32 Counter = 0; Counter != 10; ++Counter)
{
// Use Count
}
}
private:
int32 Count;
}
使用RAII资源时,实际上可能会将代码简化为影子变量,而不创建新的变量名。
例如,一个简单的记录器,它在进入和离开块时写入stdout:
class LogScope {
private:
std::string functionName;
uint32_t lineNo;
public:
LogScope(std::string _functionName, uint32_t _lineNo) :
functionName(_functionName), lineNo(_lineNo) {
std::cout << "Entering scope in " << functionName << " starting line " << lineNo << std::endl;
};
~LogScope(void) {
std:: cout << "Exiting scope in " << functionName << " starting line " << lineNo << std::endl;
};
};
它可以像这样使用:
void someFunction() { // First scope here.
LogScope logScope(__FUNCTION__, __LINE__);
// some code...
// A new block.
// There is really no need to define a new name for LogScope.
{
LogScope logScope(__FUNCTION__, __LINE__);
// some code...
}
}
话虽如此,通常不重用变量名是一种很好的做法。
Zar,
编译器会很好地处理这种情况。在您的示例中,计数变量是在两个不同的作用域"{}"中定义的。由于变量的作用域,汇编语言将引用堆栈上的两个不同地址。第一个"计数"可能是堆栈点SP-8,而内部计数可能是SP-4。一旦转换成地址,名称就无关紧要了。
我一般不会因为风格的原因而更改工作代码。如果代码一团糟,那么你就有破坏它的风险。通常混乱的代码没有任何好的测试,所以很难知道你是否破坏了它。
如果您需要增强代码,那么一定要整理一下。
--Matt
- 使用的未初始化局部变量'Quick'
- 修复未初始化的局部变量错误
- 局部变量保留函数中的值
- 如何使用 C++ 中的继承函数访问派生类中的局部变量
- 将引用分配给局部变量,如果局部变量超出范围,它会超出范围吗?
- Gnuplot_i.hpp C++接口绘制局部变量而不是文件
- 如何在函数外部访问函数中局部变量的值?
- 赋予全局变量而不是局部变量优先级的函数 - (异常行为)
- C++中静态方法的局部变量范围
- 未初始化的局部变量错误甚至认为我初始化了它(C++)
- 离开范围后如何保护局部变量的值?
- 局部变量名称冲突
- C++ lambda:如何'freeze'局部变量的值?
- 我应该使我的局部变量常量还是可移动的
- 获取具有静态局部变量的绑定/推断捕获 lambda 的函数指针
- 全局和局部变量初始化与 constexpr 的差异背后的基本原理
- 局部变量如何隐藏全局变量
- 为什么局部变量不隐藏数组定义中的全局变量
- 为什么带有类内初始值设定项的局部结构隐藏类的变量?
- 重新定义/隐藏局部变量有多糟糕