C++ 具有不同内联函数定义的函数对象的实例化取决于链接的顺序

C++ 具有不同内联函数定义的函数对象的实例化取决于链接的顺序,c++,linker,C++,Linker,请帮助我了解以下行为的根本原因 在文件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" << en

请帮助我了解以下行为的根本原因

在文件
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; }
}
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; }
}
My
main
创建一个X和一个Y,并调用它们的运算符()s:

此程序的输出取决于是先编译
a.cpp
还是
b.cpp
:在第一种情况下,来自
a.cpp
Obj
也在
NS::Y
的构造函数中实例化,在第二种情况下,来自
b.cpp
Obj
NS::X
NS::Y
中实例化

% g++ b.cpp a.cpp main.cpp
% ./a.out

X in a.cpp
X says hello
Y in b.cpp
Y says hello

% g++ b.cpp a.cpp main.cpp
% ./a.out

Y in b.cpp
X says hello
Y in b.cpp
Y says hello
Linux或Visual Studio(2005)上的链接器均未发出警告。如果我在结构声明之外定义了
Obj::pong()
,我会得到一个链接器错误,告诉我Obj::pong函数是多重定义的

我做了进一步的实验,发现原因一定与内联是否相关,因为如果我使用-O3编译,每个对象都会使用自己翻译单元中的Obj


因此,问题变成:在非优化编译期间,内联函数的第二个定义会发生什么变化?它们是否被默默忽略?

这是未定义的行为:您的类定义定义了相同的类类型,因此它们必须是相同的。对于链接器,这意味着它可以选择一个任意定义作为发出的定义

如果希望它们是分离的类型,则必须将它们嵌套到未命名的命名空间中。这将导致该名称空间中的任何内容对于该转换单元都是唯一的:

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; }
}
NS{
名称空间{
结构对象{

void pong(){cout链接器处理损坏的名称。 请看这里:

因此,正如Johannes所说,行为尚未定义,但细节可以澄清:
如果pong()是在名称空间之外定义的,则其名称将变得唯一,并且链接器会正确地进行投诉

但是,如果名称被隐藏在名称空间中,而名称空间与另一个翻译单元中的相同名称重叠(如您所知),链接器不会抱怨。它只使用一个符号。
就这样


我认为,它没有指定,并且是任何编译器/链接器的特定实现。

谢谢,我知道如何修复这种情况,但我对编译器在这种情况下的操作感兴趣。@andreas buykx,编译器除了一个定义外,不会放弃所有定义。不确定可以进一步说什么。你特别感兴趣的是什么?不可能作出此决定的是编译器,因为编译器分别处理每个cpp文件。链接器决定使用哪个实现,显然它只选择第一个。对于GCC和ELF,内联函数是弱符号()()。例如,您应该发现,如果在一个类定义中内联函数,而在另一个类定义中不内联函数,那么如果在编译时未进行优化,则应采用非内联版本。(因为链接器选择全局非弱符号而不是弱符号)。但这不是对实现行为的描述:它是关于一个特定的实现。在这里,名称空间实际上并不重要:如果我删除名称空间,一切都会按照所描述的方式进行。
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; }
}