首页 文章

使用静态变量和模板

提问于
浏览
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” . 这怎么可能?另外,一般情况下,如果我想将静态变量与模板一起使用,我应该如何处理?

5 回答

  • 22

    您're getting two copies of the same variable because you' ve在头文件中声明了一个静态变量 . 以这种方式声明全局变量 static 时,编译单元的本地're saying it'( .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的 all 实例(无论其模板参数如何),您需要使用 global variable .

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

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

    然后从你的类中访问它,如下所示:

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

    这将确保计数在使用之前被初始化,无论您从哪个编译单元访问它 . 有关详细信息,请参阅C++ FAQ on static initialization order .

  • -1

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

  • 3

    你期待"F1:1 Main:1"?您在两个单独的转换单元(即两个目标文件)中实例化 MyClass<int> ,并且链接器看到存在重复的模板实例化,因此它放弃了 f1 的目标文件中的实例化 .

    您是否将/OPT:ICF or /OPT:REF传递给VC6链接器?这可能与删除重复模板实例化有关(或者与普通重复函数相比,重复模板实例化可能是一种特殊情况) . 海湾合作委员会似乎在某些平台上做了something similar .

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

  • 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
    
  • 1

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

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

    如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中的类模板[...]成员函数可以有多个定义 . 鉴于在一个以上的翻译单元中定义了这样一个名为D的实体,那么D的每个定义应由相同的令牌序列组成;在D的每个定义中,根据3.4查找的相应名称,应指在D的定义中定义的实体,或者在重载决议(13.3)之后和部分模板专门化匹配之后应引用同一实体( 14.8.3),除非名称可以引用具有内部链接或没有链接的非易失性const对象,如果该对象在D的所有定义中具有相同的文字类型,并且该对象使用常量表达式初始化(5.19),并且该对象没有使用,并且该对象在D的所有定义中具有相同的值; [...]

    问题是在第一个 .cpp 文件中, f1 中的名称 count 指的是第二个 .cpp 文件中 f1 内的名称 count 不同的对象,因此违反了相应名称应引用同一实体的条件 .

    它们是不同的对象,因为 static 说明符表示每个翻译单元都使用该名称获得自己的对象 .

相关问题