问题1:在循环中声明变量是好做法还是坏做法?
我已经阅读了其他关于是否存在性能问题的线程(大多数人说没有),并且您应该始终在接近它们将被使用的地方声明变量。我想知道的是,这种情况是否应该避免,还是更可取。
例子:
for(int counter = 0; counter <= 10; counter++)
{
string someString = "testing";
cout << someString;
}
问题2:大多数编译器是否意识到该变量已经被声明,而只是跳过这一部分,还是每次都在内存中为它创建一个位置?
一般来说,把它放在很近的地方是一个很好的做法。
在某些情况下,出于性能等考虑,需要将变量从循环中取出。
在您的示例中,程序每次都创建并销毁字符串。一些库使用小字符串优化(SSO),因此在某些情况下可以避免动态分配。
假设你想避免这些冗余的创建/分配,你可以这样写:
for (int counter = 0; counter <= 10; counter++) {
// compiler can pull this out
const char testing[] = "testing";
cout << testing;
}
或者你可以把常数提出来
const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
cout << testing;
}
大多数编译器是否意识到该变量已经被声明,而只是跳过这一部分,还是每次都在内存中为它创建一个位置?
它可以重用变量所消耗的空间,还可以从循环中提取不变量。在const char数组(上面)的情况下-该数组可以被拉出。但是,对于对象(例如std::string),每次迭代都必须执行构造函数和析构函数。在std::string的情况下,“空格”包含一个指针,其中包含表示字符的动态分配。所以这个:
for (int counter = 0; counter <= 10; counter++) {
string testing = "testing";
cout << testing;
}
在每种情况下都需要冗余复制,如果变量位于SSO字符计数的阈值之上(并且SSO由std库实现),则需要动态分配和释放。
这样做:
string testing;
for (int counter = 0; counter <= 10; counter++) {
testing = "testing";
cout << testing;
}
在每次迭代时仍然需要一个字符的物理副本,但这种形式可能会导致一次动态分配,因为您分配了字符串,实现应该看到没有必要调整字符串的支持分配。当然,在本例中您不会这样做(因为已经演示了多个更好的替代方法),但是当字符串或向量的内容发生变化时,您可以考虑这样做。
那么,你该如何处理这些选项(以及更多选项)呢?保持它非常接近默认值—直到您充分了解成本并知道何时应该偏离。
从前(c++ 98之前);以下将中断:
{
for (int i=0; i<.; ++i) {std::string foo;}
for (int i=0; i<.; ++i) {std::string foo;}
}
警告我已经声明(foo是好的,因为它的范围在{})。这可能是人们首先认为它不好的原因。但很久以前就不是这样了。
如果你仍然要支持这样一个旧的编译器(有些人是Borland),那么答案是肯定的,一个情况下可以把i的循环,因为不这样做使得它使它“更难”的人把多个循环与相同的变量,虽然老实说编译器仍然会失败,这是所有你想要的,如果有一个问题。
如果你不再需要支持这样一个旧的编译器,变量应该保持在你能得到的最小范围内,这样你不仅可以最小化内存使用;但也使项目更容易理解。这有点像问为什么不让所有变量都是全局变量。同样的论点也适用,但作用域略有变化。
我没有发帖回答JeremyRR的问题(因为他们已经回答了);相反,我只是发表了一个建议。
对于JeremyRR,你可以这样做:
{
string someString = "testing";
for(int counter = 0; counter <= 10; counter++)
{
cout << someString;
}
// The variable is in scope.
}
// The variable is no longer in scope.
我不知道你是否意识到(我第一次开始编程时没有意识到),括号(只要它们是成对的)可以放在代码中的任何地方,而不仅仅是在“if”、“for”、“while”等后面。
我的代码编译在微软Visual c++ 2010 Express,所以我知道它的工作;此外,我已经尝试使用它在括号外定义的变量,我收到了一个错误,所以我知道变量被“销毁”。
我不知道使用这种方法是否是不好的做法,因为许多未标记的括号会很快使代码无法阅读,但也许一些注释可以澄清这些问题。
对于c++,这取决于你在做什么。
好吧,这是愚蠢的代码,但想象一下
类myTimeEatingClass
{
public:
//constructor
myTimeEatingClass()
{
sleep(2000);
ms_usedTime+=2;
}
~myTimeEatingClass()
{
sleep(3000);
ms_usedTime+=3;
}
const unsigned int getTime() const
{
return ms_usedTime;
}
static unsigned int ms_usedTime;
};
myTimeEatingClass::ms_CreationTime=0;
myFunc()
{
for (int counter = 0; counter <= 10; counter++) {
myTimeEatingClass timeEater();
//do something
}
cout << "Creating class took " << timeEater.getTime() << "seconds at all" << endl;
}
myOtherFunc()
{
myTimeEatingClass timeEater();
for (int counter = 0; counter <= 10; counter++) {
//do something
}
cout << "Creating class took " << timeEater.getTime() << "seconds at all" << endl;
}
你将等待55秒,直到你得到myFunc的输出。
因为每个循环构造函数和析构函数一起需要5秒才能完成。
你将需要5秒钟,直到你得到myOtherFunc的输出。
当然,这是一个疯狂的例子。
但它说明,当构造函数和/或析构函数需要一些时间时,执行相同构造的每个循环可能会产生性能问题。
一般来说,把它放在很近的地方是一个很好的做法。
在某些情况下,出于性能等考虑,需要将变量从循环中取出。
在您的示例中,程序每次都创建并销毁字符串。一些库使用小字符串优化(SSO),因此在某些情况下可以避免动态分配。
假设你想避免这些冗余的创建/分配,你可以这样写:
for (int counter = 0; counter <= 10; counter++) {
// compiler can pull this out
const char testing[] = "testing";
cout << testing;
}
或者你可以把常数提出来
const std::string testing = "testing";
for (int counter = 0; counter <= 10; counter++) {
cout << testing;
}
大多数编译器是否意识到该变量已经被声明,而只是跳过这一部分,还是每次都在内存中为它创建一个位置?
它可以重用变量所消耗的空间,还可以从循环中提取不变量。在const char数组(上面)的情况下-该数组可以被拉出。但是,对于对象(例如std::string),每次迭代都必须执行构造函数和析构函数。在std::string的情况下,“空格”包含一个指针,其中包含表示字符的动态分配。所以这个:
for (int counter = 0; counter <= 10; counter++) {
string testing = "testing";
cout << testing;
}
在每种情况下都需要冗余复制,如果变量位于SSO字符计数的阈值之上(并且SSO由std库实现),则需要动态分配和释放。
这样做:
string testing;
for (int counter = 0; counter <= 10; counter++) {
testing = "testing";
cout << testing;
}
在每次迭代时仍然需要一个字符的物理副本,但这种形式可能会导致一次动态分配,因为您分配了字符串,实现应该看到没有必要调整字符串的支持分配。当然,在本例中您不会这样做(因为已经演示了多个更好的替代方法),但是当字符串或向量的内容发生变化时,您可以考虑这样做。
那么,你该如何处理这些选项(以及更多选项)呢?保持它非常接近默认值—直到您充分了解成本并知道何时应该偏离。
下面的两个代码段生成相同的程序集。
// snippet 1
void test() {
int var;
while(1) var = 4;
}
// snippet 2
void test() {
while(1) int var = 4;
}
输出:
test():
push rbp
mov rbp, rsp
.L2:
mov DWORD PTR [rbp-4], 4
jmp .L2
链接:https://godbolt.org/z/36hsM6Pen
因此,除非涉及到分析或计算扩展构造函数,否则保持声明接近其用法应该是默认方法。