linking error with local derived objects in template functions
我有一个模板化函数,它使用从另一个基类派生的本地类。当这个函数在不同的编译单元中被实例化时,链接器会为默认的构造函数和析构函数抛出"多个定义"错误。
下面是一些导致我麻烦的代码的简化版本。它由三个文件组成。它应该是有效的(?)C++代码:
A.H:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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:
1 2 3 4 5 6 7 | #include"a.h" void fa() { foo a; a.f(1); } int main(int argc, char *argv[]){} |
B.CPP:
1 2 3 4 5 | #include"a.h" void fb() { foo a; a.f(1); } |
编译此函数会生成链接器错误,因为派生的构造函数和析构函数有两次:
1 2 3 4 5 | $ 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中的一个bug?我试过GCC4.3和4.4,两者都有相同的问题。
对于真正的代码,我通过将"派生"声明为全局类来解决这个问题,而不是在f中声明为本地类。但是我仍然有兴趣知道发生了什么错误以及为什么会发生这种错误,这样我就可以在将来避免它。
该规范称EDCOX1〔0〕(C++0X·93P3),因此这可能是GCC问题。
然而,它似乎在G++4.5中得到了解决,因为您的示例成功地传递了编译并与G++4.5.2链接(有或没有注释的构造函数和析构函数):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | $ 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中做了一个函数,甚至没有使用它…
1 2 3 4 5 | int test() { static int foo=3; return foo; } |
如果你试图编译这个。它会抱怨这是一个多重定义。修复它的方法是添加内联,例如
1 2 3 4 5 | inline int test() { static int foo=3; return foo; } |
这也能解决你的案件。要记住的一点是,在头文件中定义的、从多个位置包含的函数将在每个翻译单元中编译。这意味着当你链接时会有多个定义。如果您不希望它是全局函数符号,您可以像上面所做的那样使它成为内联的,或者使它成为静态的。如果
如果将其设置为静态,代码将出现在使用它的每个对象文件中。如果您使它内联,它将(潜在地)内联到每个函数中。
上面Aselle所说的是一个有趣的解释,尽管示例中的多重定义不是"f"函数,而是"派生"的局部类ctor和dtor。无论如何,作为一种替代方法,将"f"模板成员函数声明为inline可以解决gcc上的链接问题:
1 2 3 4 | struct foo { template <typename T> inline void f(const T&); }; |
如果不希望以内联方式定义所有头定义函数(这并不总是最好的态度),则可以避免使用预处理器的旧编译器上出现多个头定义函数。
1 2 3 4 | #ifndef _A_H_ #define _A_H_ // code of a.h #endif |
或者只是
1 | #pragma once |
在文件的顶部。我正在使用G++4.6,您的代码通过编译,没有错误,所以升级也是个好主意。
我用微软Visual C++ 2010测试了代码,没有错误。可能是G++的一个问题,我也必须用G++测试它来澄清。
另外,看看这个:
这些G++的"多重定义"错误是怎么回事?
应该如下所示:
1 | g++ -c -o main.o a.cpp b.cpp |