关于c ++:在模板函数中将错误与本地派生对象链接

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