【问题标题】:Proper use of undefined type in template class inheriting from abstract class在从抽象类继承的模板类中正确使用未定义类型
【发布时间】:2016-08-02 23:00:18
【问题描述】:

我有一个头文件(比如 the_foo.h),它定义/声明了以下类:

// \file the_foo.h

class FooBase
{
    virtual size_t bar_size() = 0;
};

template<class Bar>
class Foo : public FooBase
{
    size_t  bar_size()  { return sizeof(Bar); }
};

class TheBar;       // TheBar forward declaration

class TheFoo : public Foo<TheBar>
{
};

使用 MS vc14 (Visual Studio 2015) 编译时,我注意到以下行为:

  1. 任何包含the_foo.h并定义TheBar的cpp文件,例如:

    #include "the_foo.h" class TheBar {}; // TheBar definition

    会编译得很好。然而,

  2. 任何包含 the_foo.h 和 DOES NOT DEFINE TheBar 的 cpp 文件,例如:

    #include "the_foo.h"

    编译失败并出现错误:the_foo.h(11): error C2027: use of undefined type 'TheBar'

    上面编译的cpp文件只包含一行:包含标题,没有更多代码。
    删除虚拟成员函数声明 virtual size_t bar_size() = 0; 确实修复了错误。
    感谢 Sam Varshavchik 的回答,这段代码使用 gcc 5.3.1 编译得很好,所以这个问题显然是特定于编译器的。

我的问题是:

  • 为什么在情况 (2) 中编译失败,当 TheBar 仅前向声明时?
  • 有什么方法可以让案例(2)在 vc14 下编译成功,即没有明确定义 TheBar,我需要在某些 cpp 文件中保持哪个类不透明?

PS:我编辑了这个问题的代码示例,以明确导致问题中描述的问题的真正原因。原始代码示例(在 Sam Varshavchik 的回答中引用)可能确实误导了问题的实际原因,因此导致答案和 cmets 超出了问题范围。

【问题讨论】:

  • 您在这里尝试做的是创建和删除前向声明的类。编译器不知道构造函数或析构函数。所以它不应该工作。如果您需要隐藏声明,请考虑更好的对象管理系统。
  • @teivaz:据我了解 C++,我不尝试在我的示例中创建任何内容:我只包含 the_foo.h 头文件并且不实例化任何内容;如果我确实在没有定义 TheBar 的情况下实例化了 TheFoo,编译器将在 Foo() 构造函数处失败。在我的示例中,编译器在 TheFoo 析构函数中失败,而不是在构造函数中。
  • @SharpDressedMan,这里的编译器尝试生成具有所有功能的类。根据 RAII 的析构函数将调用未为前向声明的类定义的析构函数。
  • 顺便说一句,以下代码在离开范围后会崩溃:TheFoo f1, f2; f1 = f2;。您需要定义正确的复制构造函数或禁止它。

标签: c++ templates visual-c++ abstract-class forward-declaration


【解决方案1】:

使用 gcc 5.3.1 编译您的测试用例没有问题:

$ cat t.C
class FooBase
{
public:
    FooBase()          {}
    virtual ~FooBase() {}
};

template<class Bar>
class Foo : public FooBase
{
public:
    Foo()   { sizeof(Bar); bar = new Bar; }
    ~Foo()  { sizeof(Bar); delete bar; }
    Bar*    bar;
};

class TheBar;

class TheFoo : public Foo<TheBar>
{
public:
    TheFoo();
    ~TheFoo();
};
[mrsam@octopus tmp]$ g++ -c -o t.o t.C
[mrsam@octopus tmp]$ 

这里的答案似乎是“您使用的旧 C++ 编译器无法正确编译”。

【讨论】:

  • 我正在使用 MS VC14 (VS 2015) 编译器,但我预计它不会太旧...... :-)
  • 这行不通。编译器不知道如何删除 bar。
  • @Chiel:编译器也不知道如何在 Foo 构造函数中的上方行构造 bar;编译器不会从这一行报告任何错误。
  • 确实是这样,虽然比我还差:) 我以为TheFoo的构造函数和析构函数都实现了,和base一样,但是我看的不对。
【解决方案2】:

当您尝试在Foo 类的构造函数和析构函数中构造或删除class TheBar 的定义时,编译器会搜索它。这意味着它需要在那个时候实现,否则它不知道该怎么做。

如果做下面的例子:

第一个标题:

// foobase.h

class FooBase
{
    public:
        FooBase()          {}
        virtual ~FooBase() {}
};

template<class Bar>
class Foo : public FooBase
{
    public:
        Foo()  { bar = new Bar; }
        ~Foo() { delete bar; }
    private:
        Bar* bar;
};

class TheBar;

class TheFoo : public Foo<TheBar>
{
    public:
        TheFoo() {};
        ~TheFoo() {};
};

下一个标题

// thebar.h

class TheBar {};

还有以下主文件:

// foo_main.cxx

// #include "thebar.h" // Including this include makes the program run
#include "foobase.h"

int main()
{
    TheFoo thefoo;
    return 0;
}

然后你的编译器会告诉你哪里出了问题(只显示第一个错误):

./foobase.h:14:32: error: allocation of incomplete type 'TheBar'
        Foo()  { bar = new Bar; }

包含thebar.h会解决问题。

【讨论】:

  • 编译器也不知道如何在 Foo 构造函数的上面一行构造 bar;编译器不会从这一行报告任何错误。
  • 确实,而且编译器确实显示了一个错误,如果你编译我放在这里的例子你会发现。
  • 当然,在您的示例中,编译器显然会失败,因为您尝试在没有 TheBar 定义的情况下实例化 TheFoo。在我的示例中,根本没有实例化(据我所知),这似乎与您的示例完全不同。此外,在我的示例中,如果我从 FooBase 析构函数中删除 'virtual' 限定符,我的示例在 (1) 和 (2) 情况下都不会出错。
  • 如果您在声明 TheFoo 之后没有定义它(因此删除了花括号),并且您从不调用它,那么您的代码确实应该编译,但是您也可以编译为空文件。
  • otherwise it has no idea what to do 这在析构函数的情况下并不完全正确。编译器确实有一个想法。它知道析构函数必须是微不足道的,因此不需要调用析构函数,只需要释放内存。但是,如果析构函数不是微不足道的,那么程序当然是不正确的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-02
  • 1970-01-01
  • 1970-01-01
  • 2014-08-12
  • 1970-01-01
  • 2021-01-23
相关资源
最近更新 更多