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


73

我知道我可以做到:

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()RB具有RA作为公共基类。现在,如果您打算对的返回值进行任何形式的取消引用,显然您将需要在某处Foo。但是,如果某些客户端从不调用Foo,则没有理由让他们包含某处.h,这可能会大大加快编译速度。


2
@Pace协变量参考返回类型。(请参阅编辑。)但是我不确定多重继承不会使事情变得混乱。
BCS

Answers:


47

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

因此,在任何使用Bar来帮助声明Foo的情况下,您都无法转发声明Bar,并且完全包含一个基类的Forward声明是毫无意义的-这还告诉了您什么?没有?


68
“那什么都没告诉你?” 嗯,它告诉您类型是基类的子类。那不是什么。例如,如果基类中的函数偶然发现了包含子类的对象(作为“其他”对象),则它无法将该对象传递给采用基类对象的函数,因为它不知道如何即使子类可以访问子类的所有公共成员,也可以将其转换为基类。我同意您的看法,无法完成此人的要求,但我不同意您的评估,认为这样做无济于事。
codetaku '16

@codetaku:登录以评论您的评论(:对于寻求具体示例的任何人,请参见Google的C ++样式指南(在“缺点”部分中):google.github.io/styleguide/cppguide.html#Forward_Declarations
jwd

2
@codetaku-是的!例如,您希望智能指针上的static_assertenable_if来将其限制为某些类型-您想在不完整的类型上使用智能指针... bam!
davidbak

1
@jwd-我不同意Google的编码风格。信息隐藏非常有用,至少自Parnas描述以来,我们就知道。如果只有Ada的包装袋,或者limited private-但我们没有。因此,对于unique_ptr不完整类型的pimpl习惯用法,我们可以在C ++中做到最好。
davidbak

40

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

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


4
您是否需要知道类的结构才能正确处理指向它的指针?只要您专门处理参考文献,就可以了。
BCS

2
@BCS是的。只要处理指针,就只需要前向声明,但是在声明派生类时,就需要父对象的定义,而不仅是其声明,因为您不仅仅在处理指针。
乔纳森·M·戴维斯

5
这将限制对象的实现,但是我认为应该可以在仅了解继承结构的情况下进行*D->*B转换。我希望它看起来像实现虚拟基类所需的结构。
BCS 2012年

21

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

考虑以下简单情况:

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


7
我知道这是一个旧的答案,但是IMO比一个投票较高且被接受的答案更有用,他们忽略了一个事实,即使将a转换Foo*Bar*(或引用)也可能是有用的事情类型不完整。
伊万

1

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

-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();
}

2
Foo类:公共酒吧;告诉dynamic_cast <>可以工作,不是吗?我认为这将是重要的信息。
Fabian
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.