如何在命名空间中前向声明一个类

90

我试图在头文件中使用前向声明来减少#include的数量,从而在用户包含我的头文件时减少依赖性。

然而,在使用命名空间的地方我无法进行前向声明。请参见下面的示例。

文件a.hpp:

#ifndef __A_HPP__
#define __A_HPP__

namespace ns1 {

   class a {
   public:
      a(const char* const msg);

      void talk() const;

   private:
      const char* const msg_;
   };
}

#endif //__A_HPP__

文件 a.cpp:

#include <iostream>

#include "a.hpp"

using namespace ns1;

a::a(const char* const msg) : msg_(msg) {}

void a::talk() const { 
   std::cout << msg_ << std::endl; 
}

文件 consumer.hpp

#ifndef __CONSUMER_HPP__
#define __CONSUMER_HPP__

// How can I forward declare a class which uses a namespace
//doing this below results in error C2653: 'ns1' : is not a class or namespace name
// Works with no namespace or if I use using namespace ns1 in header file
// but I am trying to reduce any dependencies in this header file
class ns1::a;

class consumer
{
public:
   consumer(const char* const text) : a_(text) {}
   void chat() const;

private:
   a& a_;
};

#endif // __CONSUMER_HPP__

实现文件 consumer.cpp:

#include "consumer.hpp"
#include "a.hpp"

consumer::consumer(const char* const text) : a_(text) {}

void consumer::chat() const {
   a_.talk();
}

测试文件 main.cpp:

#include "consumer.hpp"

int main() {
   consumer c("My message");
   c.chat();
   return 0;
}

更新:

以下是我使用下面回答中的代码写出来的非常牵强的工作代码。

文件 a.hpp:

#ifndef A_HPP__
#define A_HPP__

#include <string>

namespace ns1 {

   class a {
   public:
      void set_message(const std::string& msg);
      void talk() const;

   private:
      std::string msg_;
   };

} //namespace

#endif //A_HPP__

文件 a.cpp:

#include <iostream>
#include "a.hpp"

void ns1::a::set_message(const std::string& msg) {
    msg_ = msg;
}
void ns1::a::talk() const { 
   std::cout << msg_ << std::endl; 
}

文件 consumer.hpp:

#ifndef CONSUMER_HPP__
#define CONSUMER_HPP__

namespace ns1
{
   class a;
}

class consumer
{
public:
   consumer(const char* text);
   ~consumer();
   void chat() const;

private:
   ns1::a* a_;
};

#endif // CONSUMER_HPP__

文件 consumer.cpp

#include "a.hpp"
#include "consumer.hpp"

consumer::consumer(const char* text) {
   a_ = new ns1::a;
   a_->set_message(text);
}
consumer::~consumer() {
   delete a_;
}
void consumer::chat() const {
   a_->talk();
}

文件 main.cpp

#include "consumer.hpp"

int main() {
   consumer c("My message");
   c.chat();
   return 0;
}

8
不要在宏定义中使用以两个下划线或一个下划线和一个大写字母开头的名称,这些名称是保留的。https://dev59.com/f2Qm5IYBdhLWcg3w0RxV#17307796 - Benjamin Bannier
这不仅仅是一个命名空间问题。consumer.cpp 知道有一个类类型 a,但它并不知道具体细节。你试图调用 a::talk() 方法,而编译器对此一无所知。因此,你仍然需要在 consumer.cpp 中 #include "a.hpp",以便编译器了解类的完整接口。这个 #include 将是 .cpp 内部的,因此不会通过 consumer.hpp "传播"。 - Avi Perel
3个回答

126

要在命名空间 ns1 中前向声明类类型 a

namespace ns1
{
    class a;
}

为了在多个命名空间层次中前向声明类型:

namespace ns1
{
  namespace ns2
  {
    //....
     namespace nsN
     {
        class a;
     }
    //....    
  }
}
你正在使用consumer成员中的a,这意味着它需要具体类型,你的前向声明对于这种情况不起作用。

6
+1:我已经有10年没写C++了,但我立刻知道这个问题的答案,只需重新声明命名空间即可。我很高兴我还记得这个,因为我忘记了很多其他的东西... - Binary Worrier
3
@Thomas,我刚刚尝试了一下,出现了“错误C3083:'ns1':'::'左侧的符号必须是类型”的提示。 - Dialecticus
2
@Thomas 我相信只有当命名空间已经存在时才能起作用。 - Iron Savior
3
不仅命名空间必须存在,而且类也必须在其中声明,这种情况下,在命名空间外重新声明类就没有太多意义了。 - Dialecticus

33

对于嵌套命名空间,自C++17起,您可以执行以下操作:

namespace ns1::ns2::nsN
{
  class a;
}

13
只使用 class ns1::ns2::nsN::a,怎么样? - santahopar
太好了,旧表单在嵌套类时太长了。 - pjcard
1
这肯定会很好,@santahopar,但它目前还不是有效的语法(尚未?)。或许值得在下一个C++标准中提议,可以作为class ns1::ns2::nsN::a,或者作为namespace ns1::ns2::nsN::class a - Justin Time - Reinstate Monica

4

除了像@billz所说的那样在命名空间内部前向声明该类之外,还需记得在引用前向声明的类时要么使用(前置)该命名空间,要么添加一个using子句:

// B.h
namespace Y { class A; } // full declaration of
// class A elsewhere

namespace X {
    using Y::A;   // <------------- [!]
    class B {
        A* a; // Y::A
    };
}

参考:命名空间和前向类声明


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