18

我在这样的头文件中定义了一个模板类。这里我也定义了一个静态变量:

#ifndef TEST1_H_
#define TEST1_H_

void f1();

static int count;

template <class T>
class MyClass
{
public:

    void f()
    {
        ++count;
    }


};

#endif

我在不同的 cpp 文件中定义了 main() 函数,如下所示:

int main(int argc, char* argv[])
{
    MyClass<int> a;
    a.f();
    f1();

    cout<<"Main:" << count << "\n";

    return 0;
}

我在不同的 cpp 文件中实现了函数 f1(),如下所示:

void f1()
{
    MyClass<int> a;
    a.f();

    cout<<"F1: " <<count <<"\n";
}

当我使用 VC6 编译它时,我得到的输出是“F1:0 Main:2”。这怎么可能?另外,一般来说,如果我想将静态变量与模板一起使用,我应该如何处理?

4

5 回答 5

23

因为您在头文件中声明了一个静态变量,所以您将获得同一个变量的两个副本。当你以static这种方式声明一个全局变量时,你说它是编译单元(.o文件)的本地变量。由于您将标头包含在两个编译单元中,因此您将获得两个count.

我认为您在这里真正想要的是与模板类的每个实例相关联的静态模板成员变量。它看起来像这样:

template <class T>
class MyClass
{
    // static member declaration
    static int count;
    ...
};

// static member definition
template<class T> int MyClass<T>::count = 0;

这将为您的模板的每个实例化计数。也就是说,您将拥有 , , 等的计数MyClass<int>现在MyClass<foo>看起来MyClass<bar>f1()这样:

void f1() {
    MyClass<int> a;
    a.f();

    cout<<"F1: " << MyClass<int>::count <<"\n";
}

如果您想计算 MyClass 的所有实例化(不管它们的模板参数),您确实需要使用全局变量

但是,您可能不希望直接使用全局变量,因为您冒着在初始化之前使用它的风险。您可以通过创建一个返回对您的计数的引用的全局静态方法来解决此问题:

int& my_count() {
    static int count = 0;
    return count;
}

然后像这样从您的班级中访问它:

void f() {
    ++my_count();
}

这将确保 count 在使用之前被初始化,无论您从哪个编译单元访问它。有关更多详细信息,请参阅有关静态初始化顺序的 C++ 常见问题解答

于 2009-03-03T17:28:10.553 回答
3

将静态声明放在头文件中将导致每个 .cpp 文件获得自己的变量版本。所以这两个 cout 语句正在打印不同的变量。

于 2009-03-03T17:11:27.677 回答
1

你期待“F1:1 Main:1”吗?您MyClass<int>在两个单独的翻译单元(即两个目标文件)中进行了实例化,并且链接器发现存在重复的模板实例化,因此它丢弃了在f1目标文件中的实例化。

您是传递/OPT:ICF还是/OPT:REF传递给 VC6 链接器?这可能与重复模板实例化删除有关(或不相关;与普通重复函数相比,重复模板实例化可能是一种特殊情况)。GCC 似乎在某些平台上做了类似的事情。

无论如何,我不会依赖这种行为在编译器之间保持一致。此外,在链接器命令行上更改目标文件的顺序可能会影响丢弃哪个实例化。

于 2009-03-04T07:07:30.380 回答
0

还有另一种解决方案,你可以创建一个共享父类并将这个静态变量放入其中,然后让你的模板类私有继承它,这是一个例子:

class Parent
{
protected: 
    static long count;
};

long Parent::count = 0;

template<typename T>
class TemplateClass: private Parent
{
private: 
    int mKey;
public:
    TemplateClass():mKey(count++){}
    long getKey(){return mKey;}
}

int main()
{
    TemplateClass<int> obj1;
    TemplateClass<double> obj2;

    std::cout<<"Object 1 key is: "<<obj1.getKey()<<std::endl;
    std::cout<<"Object 2 key is: "<<obj2.getKey()<<std::endl;

    return 0;
}

输出将是:

Object 1 key is: 0 
Object 2 key is: 1
于 2016-12-18T15:58:57.373 回答
-1

我认为这实际上是未定义的行为

根据 C++14 [basic.def.odr]/6:

如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中可以有多个定义类模板 [...] 的 [...] 成员函数。给定这样一个D在多个翻译单元中定义的实体,那么

  • D 的每个定义都应由相同的记号序列组成;和
  • 在 D 的每个定义中,根据 3.4 查找的相应名称应指在 D 的定义中定义的实体,或应指同一实体,经过重载解析(13.3)和部分模板特化匹配(14.8) .3),除了如果对象在 D 的所有定义中具有相同的文字类型,并且该对象使用常量表达式 (5.19) 初始化,则名称可以引用具有内部链接或没有链接的非易失性 const 对象,并且该对象不是 ODR 使用的,并且该对象在 D 的所有定义中具有相同的值;[...]

问题是在第一个.cpp文件中,其中的名称与第二个文件中的名称count所指f1的对象不同,因此违反了相应名称应指代同一实体的条件。countf1.cpp

它们是不同的对象,因为说明static符表示每个翻译单元都有自己的具有该名称的对象。

于 2015-11-16T10:26:46.770 回答