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

时间:2009-11-27 08:53:32

标签: 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" << 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.cppb.cpp:在第一种情况下,来自Obj的{​​{1}}也在{{1}内实例化}的构造函数,在第二种情况下,来自a.cpp的{​​{1}}在NS::YObj中实例化。

b.cpp

在Linux或Visual Studio(2005)上没有来自链接器的警告。如果我在结构声明之外定义NS::X,我会得到一个链接器错误,告诉我Obj :: pong函数是多重定义的。

我进一步尝试了一下,发现原因必须与内联是否有关,因为如果我用-O3编译,每个对象都使用来自他自己的翻译单元的Obj。

那么问题就变成了:在非优化编译期间内联函数的第二个定义会发生什么?他们是否默默地被忽视了?

2 个答案:

答案 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(),则其名称将变为唯一,并且链接器会正确抱怨。

但是如果名称隐藏在名称空间中,而名称空间与另一个翻译单元中的名称重叠 - 正如您已经想到的那样 - 链接器不会抱怨。它只是使用一个符号 而已。

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