【问题标题】:Compiler warning at C++ template base classC++ 模板基类的编译器警告
【发布时间】:2011-03-04 09:06:07
【问题描述】:

我收到一个编译器警告,在这种情况下我不明白。当我从以下代码编译“Child.cpp”时。 (不要奇怪:我将我的类声明剥离到最低限度,所以内容没有多大意义,但你会更快地看到问题)。我收到了最高警告级别的Visual Studio 2003Visual Studio 2008 警告。


代码

AbstractClass.h:

#include <iostream>

template<typename T>
class AbstractClass
{
    public:
        virtual void Cancel(); // { std::cout << "Abstract Cancel" << std::endl; };
        virtual void Process() = 0;
};

// Outside definition. If I comment out this and take the inline
// definition like above (currently commented out), I don't get
// a compiler warning.
template<typename T>
void AbstractClass<T>::Cancel()
{
    std::cout << "Abstract Cancel" << std::endl;
}

Child.h:

#include "AbstractClass.h"

class Child : public AbstractClass<int>
{
    public:
        virtual void Process();
};

Child.cpp:

#include "Child.h"
#include <iostream>

void Child::Process()
{
    std::cout << "Process" << std::endl;
}

警告

“Child”类派生自“AbstractClass”。在“AbstractClass”中有公共方法“AbstractClass::Cancel()”。如果我在类主体之外定义方法(就像你看到的代码一样),我会收到编译器警告...

AbstractClass.h(7) : 警告 C4505: 'AbstractClass::Cancel' : 未引用的本地函数已被删除 与 [T=int]

...当我编译“Child.cpp”时。这个我不明白,因为这是一个public函数,编译器不知道我以后有没有引用这个方法。最后,我引用了这个方法,因为我在 main.cpp 中调用它,尽管有这个编译器警告,但如果我编译并链接所有文件并执行程序,这个方法仍然有效:

//main.cpp
#include <iostream>
#include "Child.h"

int main()
{
    Child child;
    child.Cancel();  // Works, despite the warning
}

如果我确实将 Cancel() 函数定义为内联(您将其视为 AbstractClass.h 中的注释代码),那么我不会收到编译器警告。我的程序当然可以工作,但我想了解这个警告还是这只是编译器错误?

此外,如果不将 AbsctractClass 实现为模板类(在这种情况下仅用于测试目的),我也不会收到编译器警告...?


如果我创建一个非虚拟函数,我不会收到该非虚拟函数的编译警告,但到目前为止的所有答案都不包含虚拟内容。试试这个:

template<typename T>
class AbstractClass
{
    public:
        virtual void Cancel(); // { std::cout << "Abstract Cancel" << std::endl; };
        virtual void Process() = 0;
        void NonVirtualFunction();
};

//...

template<typename T>
void AbstractClass<T>::NonVirtualFunction()
{
    std::cout << "NonVirtualFunction" << std::endl;
}

知道的答案对我有帮助,但我认为问题没有得到完全回答。

【问题讨论】:

  • MSDN 有点解释了下面几篇文章中也解释的内容:msdn.microsoft.com/en-us/library/z85eyax0.aspx
  • 但在这种情况下,警告解释对我没有帮助。在任何其他情况下,警告的含义对我来说都很清楚,但在这里我的麻烦是由模板的东西引起的。无论如何感谢您的努力。
  • 如果您将Cancel() 标记为inline 而将其保留在类定义之外会怎样?
  • @sbk:如果我将其标记为内联,但将代码定义保留在外面,我不会收到警告。
  • 即使你不使用模板,但你不使用内联方法,我想如果你在Child.h之外的其他文件中使用AbstractClass.h,你可能会遇到一些错误。例如,另一个文件 AnotherChild.h 包含 AbstractClass.h,它也包含在 main.cpp 中。

标签: c++ templates compiler-warnings base-class


【解决方案1】:

我在这里看不到正确的答案:如果模板类中有纯虚方法,Visual Studio 不正确会报告此警告。在这种情况下,其他编译器(如 gcc 和 clang)似乎不会报告此警告。

模板化或非模板化类中的纯虚方法是完全合理的,而且通常是个好主意 - 将方法声明为纯虚方法会强制您在派生类中实现它。

我没有在任何地方找到对这个错误的引用 - 我不在 Microsoft 开发人员计划中,也许有人可以提交这个错误?

【讨论】:

  • 可以通过内联定义虚函数来解决该警告。
【解决方案2】:

我不认为警告是故意的。编译器错误地认为该函数是翻译单元的local,但该函数根本不是这样。您在来自main 的另一个翻译单元中使用生成的函数,因此使用了函数。您想出的使警告消失的不同方法似乎只是解决编译器中错误路径的不同方法。

virtual 的区别在于virtual 函数即使不使用它们也可以被实例化。当他们的类通常被隐式实例化时,就会发生这种情况。该标准声明有效(我强调)

实现不应隐式实例化函数模板、成员模板、非虚拟 不需要实例化的类模板的成员函数、成员类或静态数据成员。 如果虚拟成员函数不会被实例化,则未指定实现是否隐式实例化类模板的虚拟成员函数。

在这种情况下,相同虚函数有两个隐式实例化。 Child.h 中的那个没有任何用处,因此编译器认为该函数没用。但由于在其他地方(main.cpp)使用了相同的功能,该警告显然是不一致的。

【讨论】:

    【解决方案3】:

    警告只是说链接器看不到该函数的任何用法。

    如果您想“告诉”链接器避免警告,您可以欺骗链接器“认为”它正在使用中。

    例如:

    void MyLinkerThinkNotUsedFunction
    {
    }
    
    void* Foo = (void*)MyLinkerThinkNotUsedFunction;
    

    足以避免函数MyLinkerThinkNotUsedFunction上的任何警告C4505。

    【讨论】:

      【解决方案4】:

      当编译器遇到该方法的代码时,会编译普通的非模板类方法中的代码。

      对于模板类,这是不同的。代码在头文件中,所以如果编译器每次遇到代码时都会编译它,这意味着这个方法会被一遍又一遍地编译,即使你的代码没有调用它。 假设 child.h 包含在 1000 个其他文件中。您希望编译器编译 Cancel 方法 1000 次,还是仅在实际调用 Cancel 时编译?

      child.cpp 包含 child.h,但不调用 Cancel 方法。因此 Cancel 没有被编译(虽然我觉得奇怪的是你收到了警告)。

      main.cpp也包含child.h,这次调用的是Cancel方法,是编译器编译该方法的信号。最后,链接器将找到 Cancel 方法的所有已编译实例并将它们合并。

      【讨论】:

      • 感谢您的解释。在这种情况下我收到警告真的很奇怪,当然警告文本实际上具有误导性。但是,我只是编辑了我的原始条目并添加了一个非虚拟函数的行为,我没有收到警告。有了这种行为,编译器警告看起来更像是一种奇怪的编译器行为。但也许你也有一个很好的解释......?感谢您迄今为止的努力!
      • 好的,我现在明白为什么会出现警告了。如果该方法不是虚拟的,那么您确定如果它没有被调用就不需要它。如果它是虚拟的,那么它可能仍然被父类的用户调用,并且优化它可能会导致不完整的 vtable。但是,我不知道是否有办法“强制”生成代码(而不是发出警告)。
      • 您不想生成代码。你不能优化一个纯虚方法!警告是完全错误的。
      【解决方案5】:

      在 Visual Studio 2010 中,如果您为派生类(您的子类)定义构造函数,此警告会消失。

      【讨论】:

        【解决方案6】:

        模板在代码生成之前被实例化。这意味着编译器需要知道模板中使用的特定类才能为该模板生成代码。因此,当您在单独的单元中定义模板类方法时,其定义在模板实例化时是未知的。

        警告很可能意味着AbstractClass&lt;T&gt;::Cancel 的代码不是在您用于AbstractClass&lt;T&gt;::Cancel 定义的单位中生成的。模板类方法仅在使用时生成(即引用、调用),而普通方法代码则在遇到时生成。

        如果您尝试从 AbstractClass.cpp 中的函数调用 AbstractClass&lt;T&gt;::Cancel,其中定义了 Cancel,警告应该会消失。

        【讨论】:

          【解决方案7】:

          将函数模板设为虚拟是不合法的。请参阅堆栈溢出问题 Is making a function template specialization virtual legal?.

          【讨论】:

          • 但是虚函数本身并不是函数模板。我有一个包含“普通”虚函数的类模板。
          • 你不能有一个虚函数模板 - 你会在 vtable 中放置多少个插槽?但这不是函数模板!这种结构效果很好——它只发出警告,而且只在 VS 上。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-11-16
          相关资源
          最近更新 更多