与模板函数中的局部派生对象链接错误
linking error with local derived objects in template functions
我有一个模板化的函数,它使用从另一个基类派生的本地类。当这个函数在不同的编译单元中实例化时,链接器会为默认构造函数和析构函数抛出"多重定义"错误。
下面是一些给我带来麻烦的代码的简化版本。它由三个文件组成。它意味着是有效的(?)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,你的代码通过编译没有错误,所以升级也是一个好主意。
- 为什么此派生对象无法访问基类的后递减方法?
- 如何在新的派生对象中获取基本对象的数据?
- 如何将成员函数作为参数传递并在派生对象上执行方法列表
- 在没有默认构造函数的情况下创建的派生对象
- OOP 标识派生对象
- 如何创建派生对象的向量?
- 将基类分配给派生对象,反之亦然,以C++以及静态和动态对象之间的差异
- 如何从 Gtk::窗口调用派生对象的析构函数
- 无法在派生对象上运行虚拟函数
- 尝试删除指向派生对象的基指针时断言错误
- 存储在一个通用容器中的派生对象.如何避免铸造
- 将派生对象分配给函数内的基类指针
- 替换派生对象向量中的对象"no matching function to call"
- 派生对象调用的 Base 方法的模板推导
- 用于填充 Base 和派生对象的 shared_ptr 向量的函数模板
- C++ UBSAN 对派生对象产生误报
- 如何验证我是否正在使用C++中的基对象或派生对象
- C++ 构造函数和派生对象
- 将派生对象传递给模板
- 指向派生作品的基本指针,即使派生对象不存在