79

我知道我可以做到:

class Foo;

但我可以转发声明一个类继承自另一个类,例如:

class Bar {};

class Foo: public Bar;

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

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

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

// 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()基类的信息。现在很明显,如果您打算对. 但是,如果某些客户端从不调用,则它们没有理由包含可能会显着加快编译速度的某处。RBRAFooFoo

4

5 回答 5

50

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

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

于 2010-01-29T01:56:50.333 回答
40

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

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

于 2010-01-29T02:06:31.813 回答
23

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

考虑以下简单的情况:

#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 * 并对返回值进行静态转换。

于 2012-04-13T17:15:11.103 回答
2

您需要做的就是声明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必须调用 for 的复制构造函数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;
}
于 2019-12-13T20:55:59.933 回答
-1

我不认为它有用。考虑:你已经定义了一个类,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();
}
于 2010-02-02T11:22:54.497 回答