问题 实例方法中的静态变量


假设我有这个程序:

class Foo {
 public:
    unsigned int bar () {
        static unsigned int counter = 0;
        return counter++;
    }
};

int main ()
{
    Foo a;
    Foo b;
}

(当然这个例子没有意义,因为我显然将“counter”声明为私有属性,但它只是为了说明问题)。

我想知道C ++在这种情况下的行为:bar()方法中的变量“counter”对于每个实例都是一样的吗?


7293
2018-01-29 18:11


起源



答案:


是, counter 将在所有类型对象的实例之间共享 Foo 在你的可执行文件只要您处于单线程环境中,它就可以像共享计数器一样工作。

在多线程环境中,你将有一些有趣的竞争条件来调试:)。


10
2018-01-29 18:18



在多线程环境中提及危险的+1。 - Omnifarious
假设编译器尚未为您处理。语言定义是变量在对方法的所有调用中是一致的。因此,执行此操作是编译器工作,因此在多线程语言(下一版本的C ++)中,它是编译器的工作。在这个版本中,它取决于编译器与线程库的集成。 gcc已经涵盖并保证对多个线程的访问静态变量是安全的。 - Martin York
@Martin York:即C ++ 0x中的静态变量是否保证是线程本地的?有趣的是,它可能会让一些人大吃一惊。就像一个很好的静态类实例计数器。突然间,你会计算每个线程的实例。我不敢相信他们会做出那样的改变。你确定?! - Omnifarious
@Omnifarious: 怎么样 在地球上你是否从我的陈述中得到了!!这将是使用语义的彻底改变。我相信你必须完全摆脱摇滚乐才能想出这种解释。没有编译器(在语言级别)必须保证变量的语义。如果语言是多线程的,那么它必须保证对varibale的访问是线程安全的(即原子。在一个线程中int的增量必须立即在另一个线程中可用)。目前这已经被gcc保证了。 - Martin York
注意:这并不意味着需要保证对方法的访问是线程安全的。但它确实意味着不能在每个处理器的基础上缓存状态。注2:只有当语言具有内置线程的概念时,才能使用此保证(C ++没有,但C ++ 0x会)。 - Martin York


答案:


是, counter 将在所有类型对象的实例之间共享 Foo 在你的可执行文件只要您处于单线程环境中,它就可以像共享计数器一样工作。

在多线程环境中,你将有一些有趣的竞争条件来调试:)。


10
2018-01-29 18:18



在多线程环境中提及危险的+1。 - Omnifarious
假设编译器尚未为您处理。语言定义是变量在对方法的所有调用中是一致的。因此,执行此操作是编译器工作,因此在多线程语言(下一版本的C ++)中,它是编译器的工作。在这个版本中,它取决于编译器与线程库的集成。 gcc已经涵盖并保证对多个线程的访问静态变量是安全的。 - Martin York
@Martin York:即C ++ 0x中的静态变量是否保证是线程本地的?有趣的是,它可能会让一些人大吃一惊。就像一个很好的静态类实例计数器。突然间,你会计算每个线程的实例。我不敢相信他们会做出那样的改变。你确定?! - Omnifarious
@Omnifarious: 怎么样 在地球上你是否从我的陈述中得到了!!这将是使用语义的彻底改变。我相信你必须完全摆脱摇滚乐才能想出这种解释。没有编译器(在语言级别)必须保证变量的语义。如果语言是多线程的,那么它必须保证对varibale的访问是线程安全的(即原子。在一个线程中int的增量必须立即在另一个线程中可用)。目前这已经被gcc保证了。 - Martin York
注意:这并不意味着需要保证对方法的访问是线程安全的。但它确实意味着不能在每个处理器的基础上缓存状态。注2:只有当语言具有内置线程的概念时,才能使用此保证(C ++没有,但C ++ 0x会)。 - Martin York


通过“对每个实例都相同”,你的意思是在每个类实例中共享一个这个变量的实例,然后是的,这是正确的。该类的所有实例都将使用相同的变量实例。

但请记住,对于类变量,在许多情况下,您必须考虑多线程等问题,这是一个完全不同的主题。


2
2018-01-29 18:13





C ++编程语言(第2版),第200页,作者:Bjarne Stroustrup:

不要用 静态的 除了[plain]函数(第7.1.2节)和类(第10.2.4节)之外。


1
2018-01-29 18:21



规则太棒了。 但 只有在正确的背景下使用。 nieve用户可能会引用hart。如果你想引用像你那样的东西 必须 包括完整的上下文。 - Martin York


您的示例距离您可以编译和测试的内容有几行:

#include <iostream>
using namespace std;
class Foo {
 public:
    unsigned int bar () {
        static unsigned int counter = 0;
        return counter++;
    }
};

int main ()
{
    Foo a;
    Foo b;

    for (int i=0; i < 10; i++)
      cout<<i<<". "<<a.bar()<<" / "<<b.bar()<<endl;
}

输出如下:

0. 1 / 0
1. 3 / 2
2. 5 / 4
3. 7 / 6
4. 9 / 8
5. 11 / 10
6. 13 / 12
7. 15 / 14
8. 17 / 16
9. 19 / 18

所以是的,计数器在所有实例中共享。


1
2018-01-29 18:56



有趣的是,您的输出是序列点和执行顺序如何工作的完美示例,并且可能导致违反直觉的结果。 - Omnifarious


你只需要掌握两件事:

  1. 静态变量存储在执行程序的静态区域中(与全局变量的静态区域相同)。
  2. 范围受括号的一般规则限制。另外,静态变量具有内部链接。

0
2018-01-29 18:22



终身不是程序的生命。它是从第一次使用(可能永远不会)到破坏(这是静态变量创建的逆序)。 Alos注意初始化很好,因为它是函数的一部分而不是类。 - Martin York
@niel ..我的坏!!我没看到 - sud03r
最后一句是关于命名空间范围的静态变量。局部静态变量(如问题中的那些)没有链接。 (没有办法从不同的范围引用局部变量。你如何引用main中定义的变量? main()::v 不起作用,例如)。 - Johannes Schaub - litb