请帮助我了解以下行为的根本原因。
在文件a.cpp
中,我有:
namespace NS {
struct Obj {
void pong(){ cout << "X in "__FILE__ << endl; }
double k;
};
X::X() { Obj obj; obj.pong(); }
void X::operator()() { cout << "X says hello" << endl; }
}
在文件b.cpp
中,我有:
namespace NS {
struct Obj {
void pong(){ cout << "Y in "__FILE__ << endl; }
bool m;
};
Y::Y() { Obj obj; obj.pong(); }
void Y::operator()() { cout << "Y says hello" << endl; }
}
我的main
创建一个X,一个Y并调用它们的operator()s:
int main( int argc, char *argv[] )
{
NS::X x;
x();
NS::Y y;
y();
return 0;
}
此程序的输出取决于首先编译a.cpp
或b.cpp
:在第一种情况下,来自Obj
的{{1}}也在{{1}内实例化}的构造函数,在第二种情况下,来自a.cpp
的{{1}}在NS::Y
和Obj
中实例化。
b.cpp
在Linux或Visual Studio(2005)上没有来自链接器的警告。如果我在结构声明之外定义NS::X
,我会得到一个链接器错误,告诉我Obj :: pong函数是多重定义的。
我进一步尝试了一下,发现原因必须与内联是否有关,因为如果我用-O3编译,每个对象都使用来自他自己的翻译单元的Obj。
那么问题就变成了:在非优化编译期间内联函数的第二个定义会发生什么?他们是否默默地被忽视了?
答案 0 :(得分:2)
这是未定义的行为:您的类定义定义了相同的类类型,因此它们必须是相同的。对于链接器,它意味着它可以选择一个任意定义作为发出的定义。
如果希望它们是分隔类型,则必须将它们嵌套到未命名的命名空间中。这将导致该命名空间中的任何内容对于该转换单元是唯一的:
namespace NS {
namespace {
struct Obj {
void pong(){ cout << "Y in "__FILE__ << endl; }
bool m;
};
}
Y::Y() { Obj obj; obj.pong(); }
void Y::operator()() { cout << "Y says hello" << endl; }
}
那么问题就变成了:在非优化编译期间内联函数的第二个定义会发生什么?他们是否默默地被忽视了?
是的,对于内联函数(类定义中定义的函数是内联的,即使没有内联显式声明),同样的原则也适用:它们可以在程序中多次定义,并且程序的行为就像它只是定义一样一旦。对于链接器,它意味着它可以丢弃除一个定义之外的所有定义。它选择哪一个是未指定的。
答案 1 :(得分:0)
Linker处理受损的名字。 请看这里:http://en.wikipedia.org/wiki/Name_mangling
因此,正如约翰内斯所说,行为未定义,但细节可能会澄清:
如果在命名空间之外定义pong(),则其名称将变为唯一,并且链接器会正确抱怨。
但是如果名称隐藏在名称空间中,而名称空间与另一个翻译单元中的名称重叠 - 正如您已经想到的那样 - 链接器不会抱怨。它只是使用一个符号 而已。
我认为,它没有指定,并且是针对任何编译器/链接器的特定于实现的。