在C ++中,是否可以将类声明为从另一个类继承?

时间:2010-01-29 01:52:07

标签: c++ inheritance forward-declaration

我知道我能做到:

class Foo;

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

class Bar {};

class Foo: public Bar;

示例用例是共变量引用返回类型。

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

...然后在另一个不包含somewhere.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作为公共基类。现在显然你需要somewhere.h如果你打算从Foo进行任何类型的反引用返回值。但是,如果某些客户端从不调用Foo,那么它们没有理由包含somewhere.h,这可能会大大加快编译速度。

5 个答案:

答案 0 :(得分:39)

前向声明是声明,而不是定义。所以,任何需要声明类的东西(比如指向该类的指针)只需要前向声明。但是,任何需要定义的东西 - 即需要知道类的实际结构 - 都不适用于前向声明。

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

答案 1 :(得分:38)

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

因此,你不能在任何你使用它来帮助声明Foo的场景中转发声明Bar,并且它有一个包含基类的前向声明没有意义 - 这是什么除了什么都告诉你?

答案 2 :(得分:18)

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

考虑以下微不足道的案例:

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

答案 3 :(得分:0)

您需要做的就是声明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;
}

答案 4 :(得分:-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();
}