【问题标题】:no out-of-line virtual method definitions for full-specialized template class完全专业化的模板类没有外线虚方法定义
【发布时间】:2018-12-18 04:06:18
【问题描述】:

我有这种结构:

静态库 A

interface.h

class Interface
{
   public:
   virtual ~Interface() // no pure virtual dtor

   virtual void pureMethod1() = 0;
   virtual void pureMethod2() = 0;

   virtual void virtualMethod1();
   virtual void virtualMethod2();
};

interface.cpp

include "interface.h"
Interface::~Interface() = default;
Interface::virtualMethod1() {}
Interface::virtualMethod2() {}

使用 A 的静态库 B

BaseT.h

#include "interface.h"
template<class T>
class BaseT final : public Interface
{
   static_assert(false, "can't use this specialization");
};

specialized1.h

#include "baset.h"

using MyType = BaseT<CustomClass1>;

template<>
class BaseT<CustomClass1> : public Interface
{
public:
   BaseT() = default;

   void pureMethod1() final {}
   void pureMethod2() final {}
};

specialized2.h

#include "baset.h"

using MyType = BaseT<CustomClass2>;

template<>
class BaseT<CustomClass2> : public Interface
{
public:
   BaseT() = default;

   void pureMethod1() final {}
   void pureMethod2() final {}
};

我在两个完全专业化的课程中都收到了来自 clang 的警告:

警告:'BaseT 没有外联的虚拟方法定义:它的 vtable 将在每个翻译单元中发出'

为什么会出现这个警告?我没有任何纯虚拟析构函数,并且在基类中提供了默认析构函数。以及由于我使用的是模板,如何避免使用外联的虚拟方法?

【问题讨论】:

  • 您应该简化此代码以制作 mcve。由于 static_assert 失败和其他问题,现在它不会编译。
  • @PasserBy 这是一个不同的问题
  • @V.Kravchenko 是的,我的错误,没有通读。
  • 唯一的问题是所有的目标文件都会稍大一些。可执行文件中仍然只有一个 vtable。链接器负责处理(但可能需要更长的时间才能将其全部整理出来)。
  • static_assert(false, "can't use this specialization"); 使您的代码格式错误,您必须执行template &lt;typename T&gt; struct always_false : std::false_type {}; static_assert(always_false&lt;T&gt;::value, "can't use this specialization"); 之类的操作(看起来相似但正确)。

标签: c++ c++11 templates clang


【解决方案1】:

虚函数的解析通常通过使用函数指针表 vtable 来实现。每个实现(即继承自)接口的类都有这样一个表,其中条目指向其对虚拟功能的实现。该表必须至少位于编译期间生成的一个目标文件中,并且默认情况下,许多编译器将其放在包含类中第一个虚函数实现的目标文件中。

在您的情况下,BaseT 特化中的所有虚函数都在类声明中内联定义。在这种情况下,没有唯一的目标文件可以放入它们的实现,它们将包含在使用它的所有目标文件中。这反过来意味着将 vtable 与第一个虚函数的实现放在一起的方法将不再有效。这使得编译器回退到向所有目标文件添加一个 vtable 版本,只是为了安全,并让程序员意识到这一点,它会发出警告。

这不是一个真正的问题,因为链接器将从一个目标文件中选择 vtable 并包含在最终的二进制文件中。

【讨论】:

    【解决方案2】:

    出现此警告,因为您的虚函数实现位于头文件中。

    头文件只是粘贴在带有#include 指令的ccp 文件中。因此,您的文件将被粘贴到多个 cpp 文件中。并且由于在类内部提供了实现,因此每个 cpp 文件都是独立的。因此,对于每个目标文件,都会生成所有实例化

    没有简单的方法可以避免这种情况,但有一些可能的解决方案。简单的解决方案是找到使用该实现的单个 cpp。 另一个解决方法是在实现方法的地方去掉模板,即

    class ImplForCustomClass2 : public Interface
    {
    public:
        virtual void pureMethod1() final override;
        virtual void pureMethod2() final override;
    };
    template<>
    class Base<CustomClass2> : public ImplForCustomClass2
    {};
    

    不过,还有更多方法可以避免它。您可以尝试寻找更适合您的方式。

    【讨论】:

    • 纯虚方法实现是严格绑定到模板参数的,所以很遗憾我做不到
    • @Moia 很抱歉不清楚,但你可以!您只需为每个可能的有效模板参数创建 Impl 类,并为每个 BaseT 类模板实例化实现从实现事物继承
    • 对不起,我不明白。我无法在implForCustomClass2 中实现puremethod1,因为实现依赖于CustomClass2 和存储在Base&lt;CustomClass2&gt; 中的对象。我错过了什么吗?
    • 是的。好吧,据我了解,您写道CustomClass2 对象存储在Base&lt;CustomClass2&gt; 中。这不是真的。 Base&lt;CustomClass2&gt; 仅包含信息,该实现适用于 T=CustomClass2。我想说ImplForCustomClass2 是接口Interface 的实现,具有相同的pureMethod1pureMethod2 实现,正如您在示例中将写入specialized2.h 一样。 ImplForCustomClass2 与您的示例中的 Base2&lt;CustomClass2&gt; 完全相同,但没有模板,允许在 cpp 中实现其方法。
    • 我的意思是,对于CustomClass1,您将编写ImplForCustomClass1 以及CustomClass1 的相应方法实现,然后只继承class TBase&lt;CustomClass1&gt; : public ImplForCustomClass1。同样,这不是唯一的解决方法,可能还有其他解决方案可以避免您的问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-18
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 2018-08-25
    • 2013-10-27
    • 2016-11-09
    相关资源
    最近更新 更多