将对象类成员作为指针以避免在头文件中使用#include——这是一个好的做法吗?

4
这实际上是一个关于优先级的问题:在C++中,避免使用指针还是避免在头文件中使用#include更加优先?
“不要在头文件中使用#include。”
根据我的研究,似乎有一些模糊性。在这个SO问题中,排名第一的答案表示:“……确保你实际需要一个include时,当前向声明或者干脆完全不使用可以解决问题。”(来源于Header files and include best practice
而这篇文章则解释了过多的头文件引用对编译时间产生的负面影响:http://blog.knatten.org/2012/11/09/another-reason-to-avoid-includes-in-headers/ 除了本教程外,还有一句话:“……你应该尝试将所有代码放在CPP类中,只在HPP文件中声明类。”:https://github.com/LaurentGomila/SFML/wiki/Tutorial%3A-Basic-Game-Engine#wiki-declarations

“不要使用指针”。

但是,也有证据表明,大多数情况下应该避免使用指针:

哪种偏好更具优先权?

如果我对避免在头文件中使用#include的理解是正确的,那么只需将类成员更改为指针即可使用前向声明轻松实现,但这对于生命周期仅限于类本身的类成员是否是一个好主意?
1个回答

4
这并不是一个“非此即彼”的问题。两种说法都是正确的,但你需要理解背后的原因。
简而言之:尽可能使用前向声明来减少编译时间。尽可能使用堆栈对象或引用,并仅在极少数情况下使用指针。
“不要在头文件中使用 #include。”
这是一个相当普遍的说法,但实际上是错误的。这个说法背后更重要的部分是:“尽可能使用前向声明”。头文件中的包含本身并不是坏事,但通常也不需要。
如果在给定的头文件中,包含的类型/类等被用作新类型/类等声明中的指针,则可以使用前置声明。前置声明只是告诉编译器:“在某个地方你会找到类型X的实际声明。” 如果在声明中根本没有使用该类型,则可以删除该包含。原因是编译器不需要了解这些类型的任何信息来计算新类型所需的内存布局。例如,指针始终具有相同的大小。此外,在头文件中额外包含文件可能只会浪费处理能力,因为编译器必须打开和解析文件,从而在编译时间上增加昂贵的秒数。因此,在大多数情况下,通过减少头文件中不必要的包含并改用前置声明,可以使自己受益。

为了完整起见:如果出现循环引用(类A依赖于类B,类B依赖于类C,类C依赖于类A),则明确需要使用前置声明。但是,这通常也会揭示出糟糕的设计和/或旧的/过时的编码标准,这将导致我们进入第二个主题。

“不要使用指针。”

再次强调,这个语句稍微有点太笼统了。更好的说法可能是:“不要使用裸指针。”

随着C++11和即将到来的C++1y,语言本身发生了很大变化。尽管世界上有很多糟糕的C++书籍,但现在流传的C++书籍已经过时了(这里有一个好的列表)。过去我们大多数时间都被指针newdelete所困扰,用于内存管理,但现在我们已经发展出更好、更易读、风险更小且100%无内存泄漏的方法来管理内存中的数据。其中一个神奇的词是RAII - 既然你在上面链接了一些关于SFML的东西,这里有一个不错的演示RAII的威力。我看到很多人使用指针和newdelete仅仅因为或者可能是因为他们在Java或C#的术语中思考对象是如何通过new关键字实例化的。然而,在C++中,对象不需要使用new来进行分配,而且通常最好在堆栈上运行事物,而不是在堆上运行。这对于许多事情都适用,特别是当使用STL容器时,它将隐藏后台的动态管理。在大多数情况下,只有在需要数据是动态的、非“本地”的或需要大量数据时,才会使用堆。然而,当你使用堆时,请确保使用智能指针,例如std::unique_ptrstd::shared_ptr,具体取决于用例,但绝对不要使用裸指针。在现代C++中,裸指针不再拥有对象。有些情况下可以返回一个裸指针来引用一个对象,但在现代C++中没有理由调用new来创建一个裸指针。

让我们回到最初的问题。 "不要使用裸指针" 本质上是一个设计问题,与整个头文件问题无关。虽然可能有一些情况需要使用裸指针,例如由于循环引用,但前向声明的使用仅涉及编译时间(也许是为了更清晰的代码),但它对程序本身并不是必需的。
简而言之:不要使用裸指针以避免在头文件中包含,但尽可能使用前向声明并尽可能利用智能指针。

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