与模板函数中的局部派生对象链接错误

linking error with local derived objects in template functions

本文关键字:派生 对象 链接 错误 局部 函数      更新时间:2023-10-16

我有一个模板化的函数,它使用从另一个基类派生的本地类。当这个函数在不同的编译单元中实例化时,链接器会为默认构造函数和析构函数抛出"多重定义"错误。

下面是一些给我带来麻烦的代码的简化版本。它由三个文件组成。它意味着是有效的(?)c++代码:

a.h:

struct foo {
    template <typename T>
    void f(const T&);
};
struct base {
    virtual ~base(){};
};
template <typename T>
void foo::f(const T&) {
    struct derived: public base {
      // derived(){}
      // virtual ~derived(){}
    };
    derived x;
}

a.cpp:

#include "a.h"
void fa() {
    foo a;
    a.f(1);
}
int main(int argc, char *argv[]){}

b.cpp:

#include "a.h"
void fb() {
    foo a;
    a.f(1);
}

编译这个会产生一个链接错误,因为派生函数的构造函数和析构函数在那里出现了两次:

$ g++ a.cpp b.cpp
/tmp/ccvPK1l5.o: In function `void foo::f<int>(int const&)::derived::derived()':
b.cpp:(.text+0x24): multiple definition of `void foo::f<int>(int const&)::derived::derived()'
/tmp/ccRb6RYO.o:a.cpp:(.text+0x36): first defined here
[...]

有趣的是,如果您手动定义派生的构造函数和析构函数(通过取消注释这两行),一切都可以正常工作。

在我的代码中是否有任何无效的东西,或者它是gcc中的错误?我尝试了gcc 4.3和4.4,两者都有同样的问题。

对于我的实际代码,我通过将"派生"声明为全局类而不是f中的局部类来解决这种情况。但是我仍然有兴趣知道是什么出错了,为什么这样我就可以在将来避免它。

规范说Member functions of a local class (9.8) have no linkage. (c++ 0x 9.3p3),所以这可能是gcc的问题。

然而,它似乎在g++4.5中得到了解决,因为你的例子成功地通过了编译并与g++ 4.5.2链接(有或没有构造函数和析构函数注释):

$ cat a.h
struct foo {
    template <typename T>
    void f(const T&);
};
struct base {
    virtual ~base(){};
};
template <typename T>
void foo::f(const T&) {
    struct derived: public base {
      //derived(){}
      //virtual ~derived(){}
    };
    derived x;
}
$ cat a.cpp
#include "a.h"
void fa() {
    foo a;
    a.f(1);
}
int main(int argc, char *argv[]){}
$ cat b.cpp
#include "a.h"
void fb() {
   foo a;
   a.f(1);
}
$ g++ --std=c++0x --pedantic a.cpp b.cpp -o a
$ g++ -v
Using built-in specs.
COLLECT_GCC=/usr/bin/g++
COLLECT_LTO_WRAPPER=/usr/lib/x86_64-linux-gnu/gcc/x86_64-linux-gnu/4.5.2/lto-wrapper
Target: x86_64-linux-gnu
Configured with: ../src/configure -v --with-pkgversion='Ubuntu/Linaro 4.5.2-8ubuntu4' --with-bugurl=file:///usr/share/doc/gcc-4.5/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --program-suffix=-4.5 --enable-shared --enable-multiarch --with-multiarch-defaults=x86_64-linux-gnu --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib/x86_64-linux-gnu --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.5 --libdir=/usr/lib/x86_64-linux-gnu --enable-nls --with-sysroot=/ --enable-clocale=gnu --enable-libstdcxx-debug --enable-libstdcxx-time=yes --enable-plugin --enable-gold --enable-ld=default --with-plugin-ld=ld.gold --enable-objc-gc --disable-werror --with-arch-32=i686 --with-tune=generic --enable-checking=release --build=x86_64-linux-gnu --host=x86_64-linux-gnu --target=x86_64-linux-gnu
Thread model: posix
gcc version 4.5.2 (Ubuntu/Linaro 4.5.2-8ubuntu4)

我不认为这与模板有任何关系,因为这经常发生在头文件中定义的函数中。例如……如果你在你的a.h中创建了一个函数,甚至没有使用它…

   int test()
   {
        static int foo=3;
        return foo;
   }

如果你试图编译这个。它会抱怨这是一个多重定义。修复的方法是添加内联,例如

   inline int test()
   {
        static int foo=3;
        return foo;
   }

这也将解决您的情况。要记住的一点是,在头文件中定义并从多个地方包含的函数将在每个翻译单元中编译。这意味着当你链接的时候会有多个定义。如果你不想让它成为一个全局函数符号,你可以像我上面那样把它做成内联的,或者你可以把它做成静态的。如果

如果将其设置为静态,则代码将出现在使用它的每个对象文件中。如果你让它内联,它将被(潜在地)内联到每个函数中。

aselle上面说的是一个有趣的解释,尽管在您的示例中的多个定义不是'f'函数,而是'派生'本地类ctor和dtor。无论如何,作为另一种解决方法,将'f'模板成员函数声明为内联可以解决GCC中的链接问题:

  struct foo {
      template <typename T>
      inline void f(const T&);
  };

如果您不希望内联定义所有头文件定义的函数(这并不总是最好的态度),您可以使用预处理器

避免在旧的编译器上重复包含。
#ifndef _A_H_
#define _A_H_
// code of a.h
#endif

或者直接

#pragma once

放在文件的顶部。我使用的是g++ 4.6,你的代码通过编译没有错误,所以升级也是一个好主意。