我在这样的头文件中定义了一个模板类。这里我也定义了一个静态变量:
#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”。这怎么可能?另外,一般来说,如果我想将静态变量与模板一起使用,我应该如何处理?
答案 0 :(得分:22)
您获得了同一个变量的两个副本,因为您已在头文件中声明了一个静态变量。当您以这种方式声明全局变量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();
}
这将确保计数在使用之前被初始化,无论您从哪个编译单元访问它。有关详细信息,请参阅C++ FAQ on static initialization order。
答案 1 :(得分:3)
将静态声明放在头文件中将导致每个.cpp文件获得自己的变量版本。所以两个cout语句正在打印不同的变量。
答案 2 :(得分:1)
你期待“F1:1 Main:1”吗?您在两个单独的转换单元(即两个目标文件)中实例化MyClass<int>
,并且链接器看到存在重复的模板实例化,因此它放弃了f1
的目标文件中的实例化。 / p>
您是否将/OPT:ICF
or /OPT:REF
传递给VC6链接器?这可能与删除重复模板实例化有关(或者与普通重复函数相比,重复模板实例化可能是一种特殊情况)。 GCC似乎在某些平台上something similar。
无论如何,我不会依赖这种在编译器之间保持一致的行为。此外,更改链接器命令行上的目标文件的顺序可能会影响丢弃哪个实例化。
答案 3 :(得分: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
答案 4 :(得分:-1)
我认为这实际上是未定义的行为。
根据C ++ 14 [basic.def.odr] / 6:
如果每个定义出现在不同的翻译单元中,并且定义满足以下要求,则程序中的类模板的成员函数可以有多个定义[...] 。鉴于在多个翻译单元中定义了名为
D
的实体,那么
- D的每个定义应由相同的令牌序列组成;和
- 在D的每个定义中,相应的名称,根据3.4查找,应指在D的定义内定义的实体,或者在重载解析(13.3)和匹配部分模板后应引用同一实体专业化(14.8.3),但名称可以引用非易失性 如果对象在D的所有定义中具有相同的文字类型,并且使用常量表达式(5.19)初始化对象,并且对象没有使用odr,并且对象具有相同的值,则const对象具有内部链接或无链接在D的所有定义中; [...]
问题是,在第一个.cpp
文件中,count
中的名称f1
指的是与count
中f1
内的名称.cpp
不同的对象第二个static
文件,因此违反了相应名称应引用同一实体的条件。
它们是不同的对象,因为parse_url()
说明符表示每个翻译单元都有自己的具有该名称的对象。