C++中嵌套类型/类的前向声明

238

我最近遇到了这样的情况:

class A
{
public:
    typedef struct/class {…} B;
…
    C::D *someField;
}

class C
{
public:
    typedef struct/class {…} D;
…
    A::B *someField;
}

通常你可以声明一个类名:

class A;

但是你不能前向声明一个嵌套类型,以下会导致编译错误。

class C::D;
任何想法?

6
为什么你需要那个?请注意,如果它是正在定义的同一类的成员,则可以进行前向声明:class X { class Y; Y *a; }; class X :: Y { }; - Johannes Schaub - litb
1
这个解决方案对我有用(命名空间C {类D; };):https://dev59.com/TX3aa4cB1Zd3GeqPfJRn - Albert Wiersch
我找到了一个解决方案链接 - bitlixi
7个回答

267

你无法这样做,这是C++语言中的一个漏洞。你至少需要取消嵌套类中的一个类。


7
谢谢您的提问。在我的情况下,它们不是我的嵌套类。我希望通过一些前向引用来避免巨大的库头文件依赖。我想知道C++11是否解决了这个问题? - Marsh Ray
75
哦,这正是我不想让谷歌显示出来的。无论如何,感谢您提供简洁的回答。 - learnvst
22
我也有同样的问题...有人知道为什么不可能吗?似乎存在合理使用情况,这种缺陷会阻碍某些情境下的架构一致性。 - Maël Nison
你可以使用friend。并在其中加入注释,说明你正在使用它来解决C++中的漏洞。 - Erik Aronesty
5
每当我遇到这种不必要的错误时,我既想笑又想哭,这种人造语言令人难以接受。 - SongWithoutWords
是的,我刚刚尝试了前向声明顶层和嵌套类,希望这样会“起作用”:class C; class C :: D; 谷歌把我带到了这里。 :-\ - franji1

40
class IDontControl
{
    class Nested
    {
        Nested(int i);
    };
};

我需要一个类似于前向引用的东西:

class IDontControl::Nested; // But this doesn't work.

我的解决方法是:
class IDontControl_Nested; // Forward reference to distinct name.

后来当我能够使用完整的定义时:

#include <idontcontrol.h>

// I defined the forward ref like this:
class IDontControl_Nested : public IDontControl::Nested
{
    // Needed to make a forwarding constructor here
    IDontControl_Nested(int i) : Nested(i) { }
};

如果存在不平稳的复杂构造函数或其他特殊成员函数,这种技术可能会带来更多麻烦。我可以想象某些模板魔法会出现问题。

但在我这个非常简单的情况下,它似乎起作用了。


21
在C++11中,您可以通过在派生类中使用using basename::basename;来继承构造函数,因此不会出现复杂构造函数的问题。 - Xeo
1
不错的技巧,但如果在同一头文件中使用指向IDontControl :: Nested的指针(其中它被前向声明),并且从包含IDontControl的完整定义的外部代码访问,则无法正常工作(因为编译器将无法匹配IDontControl_Nested和IDontControl :: Nested)。解决方法是执行静态转换。 - Artem Pisarenko
1
我建议相反地将类放在外面,只在类内部使用 typedef - ridderhoff

5
如果您真的想避免在头文件中包含令人讨厌的头文件,可以这样做:
hpp文件:
class MyClass
{
public:
    template<typename ThrowAway>
    void doesStuff();
};

cpp文件

#include "MyClass.hpp"
#include "Annoying-3rd-party.hpp"

template<> void MyClass::doesStuff<This::Is::An::Embedded::Type>()
{
    // ...
}

但是:

  1. 在调用时您将需要指定嵌入类型(特别是如果您的函数不接受任何嵌入类型的参数)
  2. 您的函数不能是虚拟的(因为它是一个模板)

所以,是的,这是一种权衡……


1
“hpp”文件到底是什么东西? - Naftali
12
在C++项目中,**.hpp**头文件用来区分它与以.h结尾的C头文件。当在同一项目中同时使用C++和C时,有些人喜欢使用.hpp和.cpp作为C++文件的后缀,以明确区分文件类型,而将.h和.c分别用于C文件。 - Alex Bitek

2
如果您可以更改类C和D的源代码,则可以将类D单独拿出,并在类C中输入其同义词:
class CD {

};

class C {
public:

    using D = CD;

};

class CD;

1
我不会称之为答案,但仍然是一个有趣的发现: 如果你在一个名为C的命名空间中重复声明你的结构体,一切都很好(至少在gcc中)。 当找到C的类定义时,它似乎会悄悄地覆盖命名空间C。
namespace C {
    typedef struct {} D;
}

class A
{
public:
 typedef struct/class {...} B;
...
C::D *someField;
}

class C
{
public:
   typedef struct/class {...} D;
...
   A::B *someField;
}

1
我尝试使用cygwin gcc,如果您尝试引用A.someField,则无法编译。 类A定义中的C :: D实际上是指命名空间中的(空)结构体,而不是类C中的结构体。(顺便说一句,这在MSVC中也无法编译) - Dolphin
它给出了错误:"'class C' 重新声明为不同类型的符号"。 - Calmarius
9
看起来是GCC的一个bug。它似乎认为在同一作用域中,命名空间名称可以隐藏类名称。 - Johannes Schaub - litb

0

这将是一个解决方法(至少对于问题描述中的问题 - 而不是实际问题,即当无法控制C的定义时):

class C_base {
public:
    class D { }; // definition of C::D
    // can also just be forward declared, if it needs members of A or A::B
};
class A {
public:
    class B { };
    C_base::D *someField; // need to call it C_base::D here
};
class C : public C_base { // inherits C_base::D
public:
    // Danger: Do not redeclare class D here!!
    // Depending on your compiler flags, you may not even get a warning
    // class D { };
    A::B *someField;
};

int main() {
    A a;
    C::D * test = a.someField; // here it can be called C::D
}

0

这可以通过将外部类作为命名空间进行前向声明来实现。

示例:我们必须在 others_a.h 中使用一个嵌套类 others::A::Nested,而该文件不在我们的控制范围内。

others_a.h

namespace others {
struct A {
    struct Nested {
        Nested(int i) :i(i) {}
        int i{};
        void print() const { std::cout << i << std::endl; }
    };
};
}

my_class.h

#ifndef MY_CLASS_CPP
// A is actually a class
namespace others { namespace A { class Nested; } }
#endif

class MyClass {
public:
    MyClass(int i);
    ~MyClass();
    void print() const;
private:
    std::unique_ptr<others::A::Nested> _aNested;
};

my_class.cpp

#include "others_a.h"
#define MY_CLASS_CPP // Must before include my_class.h
#include "my_class.h"

MyClass::MyClass(int i) :
    _aNested(std::make_unique<others::A::Nested>(i)) {}
MyClass::~MyClass() {}
void MyClass::print() const {
    _aNested->print();
}

1
它可能有效,但没有文档支持。它有效的原因是a::b被编译器同样地处理,无论a是类还是命名空间。 - Mariusz Jaskółka
8
无法与Clang或GCC一起使用。它表示外部类被声明为与命名空间不同的东西。 - Dugi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接