繁体   English   中英

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

[英]In C++, is it possible to forward declare a class as inheriting from another class?

我知道我可以做到:

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的返回值进行任何类型的取消引用,您将需要Foo 但是,如果某些客户端从不调用Foo ,那么他们就没有理由包含可能会显着加快编译速度的 something.h 。

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

因此,您不能在任何使用它来帮助声明 Foo 的情况下转发声明 Bar,并且拥有包含基类的转发声明完全没有意义——除了告诉你什么之外没有?

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

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

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

考虑以下简单案例:

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

您需要做的就是在没有: public RA情况下声明RB (哦,还要在类定义的末尾添加; ):

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 ,该构造函数调用了 call 其成员b的默认构造函数,该成员尚未定义。 我认为你说对了。

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

更广泛地说,我认为你通过定义你的类而不是仅仅声明它们会让你自己变得更加困难。 这是一个示例,您可以在DoCleverStuff定义任何类之前使用库中的类访问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;
}

我不认为它有用。 考虑:您已经定义了一个类 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();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM