ошибка связи с локальными производными объектами в шаблонных функциях - PullRequest
3 голосов
/ 05 декабря 2011

У меня есть шаблонная функция, которая использует локальный класс, производный от другого базового класса.Когда эта функция создается в разных блоках компиляции, компоновщик выдает ошибки «множественного определения» для конструктора и деструктора по умолчанию.

Ниже приведена развернутая версия некоторого кода, которая вызывала у меня проблемы.Он состоит из трех файлов.Это должен быть действительный (?) Код C ++:

ах:

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.Но мне все равно было бы интересно узнать, что идет не так и почему, чтобы я мог избежать этого в будущем.

Ответы [ 5 ]

2 голосов
/ 05 декабря 2011

В спецификации указано 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 голосов
/ 05 декабря 2011

Я не думаю, что это имеет какое-либо отношение к шаблонам, потому что это часто происходит с функциями, которые определены в заголовочных файлах. Например ... если вы сделали функцию в вашем a.h и даже не использовали ее ...

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

Если вы попытаетесь скомпилировать это. Он будет жаловаться на то, что это множественное определение. Чтобы исправить это, добавьте inline, например,

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

Это также исправит ваш случай. Следует помнить, что функция, определенная в заголовочном файле и включенная из нескольких мест, будет скомпилирована в каждом модуле перевода. Это означает, что при ссылке будет несколько определений. Если вы не хотите, чтобы это был символ глобальной функции, вы можете сделать его встроенным, как я делал выше, или вы можете сделать его статическим. Если

вы делаете его статическим, код будет появляться в каждом объектном файле, который его использует. Если вы сделаете это встроенным, оно будет встроено (потенциально) в каждую функцию.

1 голос
/ 05 декабря 2011

То, что aselle сказал выше, является интересным объяснением, хотя множественное определение в вашем примере - это не функция 'f', а 'производный' локальный класс ctor и dtor.В любом случае, в качестве альтернативного обходного пути, объявление функции-члена шаблона 'f' как встроенной решает проблему компоновки в GCC:

  struct foo {
      template <typename T>
      inline void f(const T&);
  };
0 голосов
/ 05 декабря 2011

Если вы не хотите определять все встроенные в заголовок функции (это не всегда лучший подход), вы избегаете многократного включения в более старые компиляторы, использующие препроцессор

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

или, альтернативно, просто

#pragma once

в верхней части файла.Я использую g ++ 4.6, и ваш код проходит компиляцию без ошибок, поэтому обновление также является хорошей идеей.

0 голосов
/ 05 декабря 2011

Я протестировал ваш код в Microsoft Visual C ++ 2010, и ошибок не было.Может быть проблема с g ++, я должен был бы проверить это также с g ++ для пояснения.

Кроме того, взгляните на это:

Что с этими g ++ "множественное определение"ошибки?

Должно выглядеть так:

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