【问题标题】:In C++, is it possible to forward declare a class as inheriting from another class?在 C++ 中,是否可以将一个类声明为从另一个类继承?
【发布时间】:2011-01-10 16:34:57
【问题描述】:

我知道我能做到:

class Foo;

但我可以将一个类声明为从另一个类继承吗,例如:

class Bar {};

class Foo: public Bar;

一个示例用例是协变引用返回类型。

// somewhere.h
class RA {}
class RB : public RA {}

...然后在另一个不包含某处的标题中。h

// other.h
class RA;

class A {
 public:
  virtual RA* Foo();  // this only needs the forward deceleration
}

class RB : public RA; // invalid but...

class B {
 public:
  virtual RB* Foo();  // 
}

编译器应该处理RB* B:Foo() 声明的唯一信息是RB 具有RA 作为公共基类。现在很明显,如果您打算对来自Foo 的返回值进行任何类型的取消引用,您将需要somewhere.h。但是,如果某些客户端从不调用Foo,那么它们就没有理由包含可能显着加快编译速度的somewhere.h。

【问题讨论】:

标签: c++ inheritance forward-declaration


【解决方案1】:

前向声明仅在告诉编译器具有该名称的类确实存在并且将在其他地方声明和定义时才真正有用。在编译器需要关于类的上下文信息的任何情况下,您都不能使用它,编译器也不能只告诉它关于类的一点点信息。 (通常,您只能在没有其他上下文的情况下引用该类时使用前向声明,例如作为参数或返回值。)

因此,在使用 Bar 来帮助声明 Foo 的任何情况下,您都不能前向声明 Bar,而且包含基类的前向声明完全没有意义——那是什么什么都不告诉你?

【讨论】:

  • “除了什么也没有告诉你什么?”呃,它告诉你类型是基类的子类。那不是什么。例如,如果你的基类中的一个函数偶然发现了一个包含你的子类的对象(作为“其他”对象),那么它就不能将该对象传递给一个接受基类对象的函数,因为它不知道如何将子类转换为基类,即使它可以访问子类的所有公共成员。我同意你的观点,不可能按照这个人的要求去做,但我不同意你认为这样做是没有用的。
  • @codetaku:登录以支持您的评论(:对于任何寻求具体示例的人,请参阅 Google 的 C++ 样式指南(在“缺点”部分):google.github.io/styleguide/cppguide.html#Forward_Declarations
  • @codetaku - 是的!例如,您希望在智能指针上使用 static_assertenable_if 以将其限制为某些类型 - 您想在不完整的类型上使用智能指针...... bam!
  • @jwd - 我不同意 Google 的编码风格。信息隐藏很有用,至少从Parnas described it 开始我们就知道了。如果我们有 Ada 的包体,或者limited private- 但我们没有。因此,对于 pimpl 习惯用法,将 unique_ptr 转换为不完整的类型是我们在 C++ 中可以做的最好的事情。
【解决方案2】:

前向声明是声明,而不是定义。因此,任何需要声明类的东西(如指向该类的指针)只需要前向声明。但是,任何需要定义的东西——即需要知道类的实际结构——都不能只使用前向声明。

派生类肯定需要知道其父类的结构,而不仅仅是父类存在,因此前向声明是不够的。

【讨论】:

  • 您是否需要知道类的结构才能正确处理指向它的指针?只要您只处理参考文献就可以了。
  • @BCS 是的。只要你在处理指针,你只需要前向声明,但是当你声明一个派生类时,你需要父类的定义,而不仅仅是它的声明,因为你不只是在处理指针。
  • 它会限制对象的实现,但我认为应该可以在只知道继承结构的情况下进行*D -> *B 转换。我希望它看起来像实现虚拟基类所需的结构。
【解决方案3】:

不,即使您只处理指针,也不可能转发声明继承。在处理指针之间的转换时,有时编译器必须知道类的细节才能正确地进行转换。多重继承就是这种情况。 (您可以特殊情况下仅使用单一继承的层次结构的某些部分,但这不是语言的一部分。)

考虑以下简单的情况:

#include <stdio.h>
class A { int x; };
class B { int y; };
class C: public A, public B { int z; };
void main()
{ 
    C c; A *pa = &c; B *pb = &c; C *pc = &c; 
    printf("A: %p, B: %p, C: %p\n", pa, pb, pc);
}

我收到的输出(使用 32 位 Visual Studio 2010)是:

A: 0018F748, B: 0018F74C, C: 0018F748

所以对于多重继承,在相关指针之间进行转换时,编译器必须插入一些指针算法才能正确转换。

这就是为什么,即使你只处理指针,你也不能转发声明继承。

至于为什么它有用,当您确实想使用协变返回类型而不是使用强制转换时,它会缩短编译时间。例如这不会编译:

class RA;
class A             { public: virtual RA *fooRet(); };
class RB;
class B : public A  { public: virtual RB *fooRet(); };

但这会:

class RA;
class A             { public: virtual RA *fooRet(); };
class RA { int x; };
class RB : public RA{ int y; };
class B : public A  { public: virtual RB *fooRet(); };

当您有 B 类型的对象(不是指针或引用)时,这很有用。在这种情况下,编译器足够聪明,可以使用直接函数调用,并且您可以直接使用 RB* 的返回类型而无需强制转换。在这种情况下,通常我会继续将返回类型设为 RA * 并对返回值进行静态转换。

【讨论】:

  • 我知道这是一个旧答案,但 IMO 这是一个比投票率更高和接受的答案更有帮助的答案,它们忽略了将 Foo* 转换为 Bar* 的事实(或引用)即使是不完整的类型也是有用的。
【解决方案4】:

您需要做的就是声明 RB 而不使用 : public RA(哦,还要在类定义的末尾添加 ;):

class RA;

class A {
    public:
    virtual RA* Foo();
};

class RB;

class B {
public:
    virtual RB* Foo();
};

// client includes somewhere.h
class RA {};
class RB : public RA {};

int main ()
{
    return 0;
}

但是,这并不能解决 user1332054 的回答中很好地描述的具体问题。

其他一些答案似乎表明了我想消除的一些误解:

当我们知道定义不太可能被包含时,向前声明有用的甚至。这允许我们在我们的库中进行大量类型推断,使它们与许多其他已建立的库兼容,而不包括它们。包含不必要的库会导致过多的嵌套包含,这可能会增加编译时间。最好在适当的时候使您的代码兼容,并尽可能少地包含。

通常,您可以定义一个带有指向仅已声明但未定义的类的指针的类。示例:

struct B;

struct A
{
    B * b_;

    B * foo ()
    {
        return b_;
    }

    B & foo (B * b)
    {
        return *b;
    }
};

int main ()
{
    return 0;
}

上面的编译很好,因为编译器不需要知道关于 B 的任何信息。

一个可能有点难以意识到编译器需要更多信息的例子:

struct B;

struct A
{
    B * foo ()
    {
        return new B;
    }
};

上述问题是因为new B调用了尚未定义的B::B()构造函数。另外:

struct B;

struct A
{
    void foo (B b) {}
};

这里foo 必须调用b 的复制构造函数,它还没有被定义。最后:

struct B;

struct A
{
    B b;
};

这里我们用默认构造函数隐式定义了A,它调用了调用其成员的默认构造函数b,尚未定义。我想'你明白了。

所以,关于 user1332054 描述的更普遍的问题,老实说,我不明白为什么不能在继承的虚函数中使用指向未定义类的指针。

但更广泛地说,我认为您通过定义您的类而不是仅仅声明它们使自己变得更加困难。这是一个示例,在您完全定义任何类之前,您可以使用库中的类到达 DoCleverStuff

// Just declare

class RA;
class RB;

class A;
class B;

// We'll need some type_traits, so we'll define them:

template <class T>
struct is_type_A
{
    static constexpr bool value = false;
};

template <>
struct is_type_A <A>
{
    static constexpr bool value = true;
};

template <class T>
struct is_type_B
{
    static constexpr bool value = false;
};

template <>
struct is_type_B <B>
{
    static constexpr bool value = true;
};

#include <type_traits>

// With forward declarations, templates and type_traits now we don't
// need the class definitions to prepare useful code:

template<class T>
typename std::enable_if<is_type_A<T>::value, RA *>::type
DoCleverStuff (T & t)
{
    // specific to A

    return t.fooRet();
}

template<class T>
typename std::enable_if<is_type_B<T>::value, RB *>::type
DoCleverStuff (T & t)
{
    // specific to B

    return t.fooRet();
}

// At some point the user *does* the include:

class RA
{
    int x;
};

class RB : public RA
{
    int y;
};

class A
{
public:
    virtual RA * fooRet()
    {
        return new RA;
    }
};

class B : public A
{
public:

    virtual RB * fooRet()
    {
        return new RB;
    }
};

int main ()
{
    // example calls:

    A a;

    RA * ra = DoCleverStuff(a);

    B b;

    RB * rb = DoCleverStuff(b);

    delete ra;
    delete rb;

    return 0;
}

【讨论】:

    【解决方案5】:

    我认为它没有用。考虑一下:你已经定义了一个类,Bar:

    class Bar {
    public:
        void frob();
    };
    

    现在你声明一个类 Foo:

    class Foo;
    

    你可以对 Foo 做的就是构造一个指向它的指针。现在,假设您添加了Foo 派生自Bar 的信息:

    class Foo: public Bar;
    

    你现在能做什么以前不能做的事?我认为您所能做的就是接受指向Foo 的指针并将其转换为指向Bar 的指针,然后使用该指针。

    void frob(Foo* f) {
        Bar *b = (Bar)f;
        b->frob();
    }
    

    但是,您必须在其他地方生成指针,因此您可以直接接受指向 Bar 的指针。

    void frob(Bar* b) {
        b->frob();
    }
    

    【讨论】:

    • 类Foo:公共酒吧;告诉 dynamic_cast 会起作用,不是吗?我认为这将是一条重要的信息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-01-04
    • 1970-01-01
    • 1970-01-01
    • 2012-08-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多