首页 文章

显式实例化的类模板中的自动构造函数

提问于
浏览
8

我在头文件( obj.h )中使用显式自动移动构造函数( = default )声明了 template<bool VAR> struct Obj 模板 .

// obj.h
#pragma once
#include <vector>

template<bool VAR>
struct Obj {
  std::vector<int> member;
  Obj(int m): member(m) { }
  Obj(Obj&&) = default;
  int member_fun() const;
};

extern template struct Obj<false>;
extern template struct Obj<true>;

模板的成员函数在另一个文件( obj.cpp )中定义,并显式实例化模板:

// obj.cpp
#include "obj.h"

template<bool VAR>
int Obj<VAR>::member_fun() const {
  return 42;
}

template struct Obj<false>;
template struct Obj<true>;

然后从主文件( main.cpp )使用此模板:

// main.cpp
#include <utility>
#include "obj.h"

int main() {
  Obj<true> o1(20);
  Obj<true> o2(std::move(o1));
  return o2.member_fun();
}

然后编译 .cpp 并使用以下 Makefile 链接在一起:

#CXX=clang++
CXX=g++
CXXFLAGS=-Wall -Wextra -std=c++14

a.out: obj.o main.o
    $(CXX) $(CXXFLAGS) $^ -o a.out

obj.o: obj.cpp obj.h
    $(CXX) $(CXXFLAGS) -c $< -o $@
main.o: main.cpp obj.h
    $(CXX) $(CXXFLAGS) -c $< -o $@

但是,我收到一个链接器错误: undefined reference to 'Obj<true>::Obj(Obj<true>&&)' - 编译器显然没有实例化构造函数 .

  • Obj<true>::member_fun() 已定义,如果我从 main.cpp 删除对移动构造函数的引用,程序确实链接成功 .

  • 如果我从 Headers 中删除 extern template ,程序将编译 .

  • 如果我使用 int 而不是 std::vector<int> 作为 member 的类型,程序也会编译 .

  • cppreference.com声称“编译器会将移动构造函数声明为其类的非显式内联公共成员” . 但是,手动定义的 Obj(int) 构造函数也是内联的,但它已正确实例化 .

(我在一个用GCC编译得很好的项目中收到了Clang这个错误,所以我认为这是一个Clang错误 . 但是,当我把问题简化为这个简单的情况时,GCC 5.4.0和Clang 3.8.0都产生相同的错误结果) .

3 回答

  • 2

    有趣 . 我认为你的代码是正确的,因为:

    你的默认移动构造函数是隐含的 inline ,因为:

    [dcl.fct.def.default]/5

    ...用户提供的显式默认函数(即,在其第一次声明后显式默认)是在明确默认的位置定义的 .

    [class.mfct]/1

    可以在其类定义中定义成员函数([dcl.fct.def]),在这种情况下,它是内联成员函数([dcl.fct.spec])

    因此根据[temp.explicit]/10(强调我的)免除显式模板实例化:

    除了内联函数和变量,从其初始化程序或返回值([dcl.spec.auto])推导出的类型的声明,文字类型的常量变量,引用类型的变量和类模板特化,显式实例化声明都有效果抑制它们引用的实体的隐式实例化 . [注意:意图是当使用odr([basic.def.odr])时,仍然会隐式实例化作为显式实例化声明主题的内联函数,以便可以考虑使用内联体,但是没有将在翻译单元中生成内联函数的外联副本 . - 结束说明]

    实际上,如果您尝试除 -O0 之外的任何优化模式,问题就会消失 .

    -O0 是一种特殊模式,其中内联函数未内联 . 但是没关系,编译器必须在这种情况下生成 inline 默认的移动构造函数,就像它与其他构造函数一样 .

    所以对我来说这看起来像编译器错误 . 另请查看LLVM#22763GCC#60796 .

    我看到至少2种可能的解决方法:

    Solution 1

    现在不要使用 extern template ... (编译时间会受到影响,但没有什么大不了的) .

    Solution 2

    欺骗编译器在 obj.cpp 中生成抑制的构造函数

    template<> Obj<true>::Obj(Obj&&) noexcept = default;
    

    这个只用于 -O0 模式 . 在 生产环境 代码中,将使用内联版本 .

  • 0

    互联网上没有关于这个主题的很多信息 . 虽然看一些消息来源,但我会得出以下结论:

    extern template 应该阻止隐式实例化,尽管在所有示例中, explicit 实例化都没有此 extern template 定义 .

    从我可以阅读的内容,特别是关于提案和GCC邮件列表(参见下面的参考资料), extern template 并不阻止 implicit 实例化,尽管模板的所有实例化 . 这将包括您的 explicit 实例化 .

    如果实体是同一翻译单元中的显式实例化声明和显式实例化定义的主题,则该定义应遵循声明 . - John Spicer在GCC邮件列表上

    从这里,我得出结论,你应该删除你想要显式实例化的翻译单元中的 extern template .

    参考文献:

  • 5

    您可能遇到编译器错误 .

    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60796 .

    我在CLang上遇到了类似的行为,但找不到错误报告 .

相关问题