【问题标题】:Automatic constructor in explicitly instantiated class template显式实例化的类模板中的自动构造函数
【发布时间】:2016-08-13 06:35:06
【问题描述】:

我在带有显式自动移动构造函数 (= default) 的头文件 (obj.h) 中声明了一个 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();
}

然后将.cpps 编译并与以下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&lt;true&gt;::Obj(Obj&lt;true&gt;&amp;&amp;)'——编译器显然没有实例化构造函数。

  • Obj&lt;true&gt;::member_fun() 已定义,如果我从 main.cpp 删除对移动构造函数的引用,程序确实链接成功。
  • 如果我从标题中删除extern template,程序就会编译。
  • 如果我使用int 而不是std::vector&lt;int&gt; 作为member 的类型,程序也会编译。
  • cppreference.com 声称“编译器会将移动构造函数声明为其类的非显式 inline 公共成员”。但是,手动定义的Obj(int) 构造函数也是内联的,但它被正确实例化了。

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

【问题讨论】:

  • 原来用GCC编译的项目是用C++11还是C++98?
  • 它使用 C++14 (-std=c++14)。
  • 我猜这与显式实例化和内联有关。有一个 vector 可能会使默认的构造函数非内联。
  • @skypjack:没有必要在同一个文件中定义模板,只要模板的每次使用都在程序的某个目标文件中实例化,我在@中通过显式实例化来完成987654343@文件。
  • @rustyx 是的,可能有一些模板、inline 和自动定义的交互。然而,cppreference.com (en.cppreference.com/w/cpp/language/move_constructor) 说“......编译器将声明一个移动构造函数作为其类的非显式 inline 公共成员......”。请注意,我手动定义的另一个构造函数Obj(int)也是inline,但它是在main.cpp中定义的。

标签: c++ templates c++11


【解决方案1】:

有趣。我认为您的代码是正确的,因为:

您的默认移动构造函数隐式为inline,因为:

[dcl.fct.def.default]/5:

... 用户提供的显式默认函数(即显式 在第一次声明后默认)被定义在 它是明确默认的。

还有[class.mfct]/1

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

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

内联函数和变量除外,带有类型的声明 从它们的初始值设定项或返回值 ([dcl.spec.auto]) 推导出来, 字面量类型的 const 变量、引用类型的变量和 类模板特化,显式实例化声明 具有抑制隐式实例化的效果 他们所指的实体。 [注意:意图是内联 作为显式实例化声明主题的函数 使用 odr 时仍会隐式实例化 ([basic.def.odr]) 这样可以考虑将主体进行内联,但是没有 内联函数的外联副本将在 翻译单元。 ——尾注]

其实,如果你尝试-O0以外的任何优化模式,问题就消失了。

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

所以对我来说,这看起来像是一个编译器错误。另请查看LLVM#22763GCC#60796

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

解决方案 1

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

解决方案 2

诱使编译器在obj.cpp中生成被抑制的构造函数

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

这个只会在-O0模式下使用。在生产代码中,将使用内联版本。

【讨论】:

  • 解决方案3:仅在一个翻译单元中定义移动构造函数。头文件:Obj(Obj&amp;&amp;) noexcept;cpp文件:template &lt;bool VAR&gt; Obj&lt;VAR&gt;::Obj(Obj&amp;&amp;) noexcept = default;
  • 如果你可以让你的编译器跨模块内联,这也将起作用(我对此的了解是有限的,但至少 GCC 不会,即使在 -O3 模式下也是如此)。否则此解决方案将影响生产代码的性能。
【解决方案2】:

互联网上没有很多关于这个主题的信息。虽然查看了一些资料,但我会得出以下结论:

extern template 应该防止隐式实例化,尽管在所有示例中,explicit 实例化都没有这个 extern template 定义。

据我所知,特别是在提案和 GCC 邮件列表(参见下面的参考资料)中,extern template 不会阻止 implicit 实例化,尽管模板的所有实例化。这将包括您的 explicit 实例化。

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

据此,我得出的结论是,您应该在需要显式实例化的翻译单元中删除 extern template

参考资料:

【讨论】:

  • 如果我理解正确,省略显式实例化声明应该允许编译器隐式实例化它在编译 main.cpp 时看到的成员函数定义(即 inline 函数,包括两个构造函数),而显式实例化定义确保obj.cpp中定义的非inline成员将正确生成?
  • 我没有尝试过,但从我所读到的,将“外部模板”与显式模板一起使用会导致显式被省略。因此,您应该将“外部模板”放入执行该显式模板实例化的翻译单元中。
【解决方案3】:

您可能遇到了编译器错误。

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

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

【讨论】:

    猜你喜欢
    • 2013-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多