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

发布于 2024-08-19 17:13:45 字数 746 浏览 3 评论 0原文

我知道我可以这样做:

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() 的声明应该需要的唯一信息是RBRA 作为公共基类。现在显然,如果您打算对 Foo 的返回值进行任何类型的取消引用,您将需要 Something.h。但是,如果某些客户端从不调用 Foo,那么它们就没有理由包含 somewhere.h,这可能会显着加快编译速度。

I know that I can do:

class Foo;

but can I forward declare a class as inheriting from another, like:

class Bar {};

class Foo: public Bar;

An example use case would be co-variant reference return types.

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

... and then in another header that doesn't include 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();  // 
}

The only information the compiler should need to process the declaration of RB* B:Foo() is that RB has RA as a public base class. Now clearly you would need somewhere.h if you intend to do any sort of dereferencing of the return values from Foo. However, if some clients never calls Foo, then there is no reason for them to include somewhere.h which might significantly speed compilation.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

疑心病 2024-08-26 17:13:46

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

I don't think it's useful. Consider: you have defined a class, Bar:

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

Now you declare a class Foo:

class Foo;

All you can do with Foo is construct a pointer to it. Now, suppose you add the information that Foo is derived from Bar:

class Foo: public Bar;

What can you now do that you couldn't do before? I think that all you can do is accept a pointer to Foo and cast it into a pointer to Bar, then use that pointer.

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

However, you must have generated the pointer elsewhere, so you could have just accepted a pointer to Bar instead.

void frob(Bar* b) {
    b->frob();
}
无人问我粥可暖 2024-08-26 17:13:45

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

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

A forward declaration is only really useful for telling the compiler that a class with that name does exist and will be declared and defined elsewhere. You can't use it in any case where the compiler needs contextual information about the class, nor is it of any use to the compiler to tell it only a little bit about the class. (Generally, you can only use the forward declaration when referring to that class without other context, e.g. as a parameter or return value.)

Thus, you can't forward declare Bar in any scenario where you then use it to help declare Foo, and it flat-out doesn't make sense to have a forward declaration that includes the base class -- what does that tell you besides nothing?

爱人如己 2024-08-26 17:13:45

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

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

Forward declarations are declarations, not definitions. So, anything that requires the declaration of a class (like pointers to that class) need only the forward declaration. However, anything that would require the definition - i.e. would need to know the actual structure of the class - would not work with just the forward declaration.

Derived classes definitely need to know the structure of their parent, not just that the parent exists, so a forward declaration would be insufficient.

蓝色星空 2024-08-26 17:13:45

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

考虑以下简单情况:

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

No it is not possible to forward declare inheritance, even if you are only dealing with pointers. When dealing with conversions between pointers, sometimes the compiler has to know the details of the class to do the conversion correctly. This is the case with multiple inheritance. (You could special case some parts parts of the hierarchy that only use single inheritance, but that isn't part of the language.)

Consider the following trivial case:

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

The output I received (using 32 bit visual studio 2010), is:

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

So for multiple inheritance, when converting between related pointers, the compiler has to insert some pointer arithmetic to get the conversions right.

This is why, even if you are dealing only with pointers, you can't forward declare inheritance.

As for why it would be useful, it would improve compile times when you do want to make use of co-variant return types instead of using casts. For example this will not compile:

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

But this will:

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

This is useful when you have objects of type B (not pointers or references). In this case the compiler is smart enough to use a direct function call, and you can use the return type of RB* directly without casting. In this case, usually I go ahead and make the return type RA * and do a static cast on the return value.

指尖上的星空 2024-08-26 17:13:45

您所需要做的就是声明不带 : public RARB (哦,还要将 ; 添加到类定义的末尾):

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

All you needed to do was declare RB without the : public RA (oh, and also add ; to the end of your class definitions):

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

However, this doesn't solve the specific problem described nicely in the answer by user1332054.

Some of the other answers appear to show some misconceptions that I'd like to dispel:

Forward declaring is useful even when when we know that the definition is not likely to be included. This allows us to do a lot of type-deduction in our libraries that make them compatible with many other established libraries without including them. Including libraries unnecessarily leads to too many nested includes that can explode the compile time. It's good practice to make your code compatible when appropriate, and to include as little as possible.

Typically you can define a class with pointers to classes that have only been declared and not defined. Example:

struct B;

struct A
{
    B * b_;

    B * foo ()
    {
        return b_;
    }

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

int main ()
{
    return 0;
}

The above compiles fine, because the compiler doesn't need to know anything about B.

An example where it might be a bit harder to realise that the compiler needs more information:

struct B;

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

The above problem is because new B invokes the B::B() constructor which hasn't been defined yet. Also:

struct B;

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

Here foo must call the copy constructor for b, which also hasn't been defined yet. Lastly:

struct B;

struct A
{
    B b;
};

Here we have implicitly defined A with the default constructor, which calls the default constructor of call its members,b, which hasn't been defined yet. I think' you get the point.

So, in reference to the more general problem, described by user1332054, I honestly don't understand why it's not possible to use pointers to undefined classed in an inherited virtual function.

More broadly though, I think that you're making it more difficult for yourself by defining your classes instead of only declaring them. Here's an example where you get to DoCleverStuff with your classes in your library before you've defined any of your classes at all:

// 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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文