将错误与模板函数中的本地派生对象相关联

时间:2011-12-05 04:06:23

标签: c++ templates linker g++

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

以下是一些导致我麻烦的代码的简化版本。它由三个文件组成。它应该是有效的(?)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,两者都有同样的问题。

对于我的真实代码,我通过声明&#34;衍生的&#34;解决了这种情况。作为一个全球阶级,而不是f内部的本地阶级。但是我知道出了什么问题仍然很有趣,为什么我将来可以避免它。

5 个答案:

答案 0 :(得分:2)

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

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

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

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

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

你使它静态,代码将出现在使用它的每个目标文件中。如果你使它内联,它将被内联(可能)到每个函数中。

答案 1 :(得分:2)

规范说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)

答案 2 :(得分:1)

上面说的aselle是一个有趣的解释,虽然你的例子中的多重定义不是'f'函数,而是'派生'本地类ctor和dtor。无论如何,作为替代解决方法,将'f'模板成员函数声明为内联解决了GCC上的链接问题:

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

答案 3 :(得分:0)

我使用Microsoft Visual C ++ 2010测试了您的代码并且没有错误。可能是g ++的一个问题,我还要用g ++进行测试以便澄清。

另外,看看这个:

What's with these g++ "multiple definition" errors?

应该是这样的:

 g++ -c -o main.o a.cpp b.cpp 

答案 4 :(得分:0)

如果您不希望内联定义所有标头定义的函数(它并不总是最好的态度),那么您可以避免在使用预处理器的旧编译器中包含多个函数

#ifndef _A_H_
#define _A_H_
// code of a.h
#endif

或者只是

#pragma once

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