C++ 中的前向声明- 什么时候有关系?

发布于 2024-12-14 03:12:47 字数 1877 浏览 4 评论 0原文

我认为这是 C++ 的精神——你不为你不做的事付出代价 想要(你明确地为你需要的东西付费):

// a.h
#include <iosfwd>

template< class T >
class QVector;

struct A
{
void process( QVector<int> );

void print( std::ostream& );
};

// some.cpp

#include "a.h"

#include <iostream> // I need only A::print() in this module, not full interface

...
A().print( std::cout );
...

这就是为什么我认为禁止开发人员工作是不公平的 这样使用STL( C++11 STL 是否有前向声明文件?)。

但我也看到一件坏事:模块 A 的依赖项 将在外部上下文中传播(#include指令的重复),并且可能导致硬重构 当界面发生变化时(例如,将 QVector 替换为 QList - 现在您需要将所有出现的 替换为 )。

这个问题的解决方案

#include <iostream>
#include <QVector>

struct A
{
void process( QVector<int> );

void print( std::ostream& );
};

我们是否应该将此称为习语“接口的基本类型” - 模块 接口的类型应该像基本类型一样(总是 已定义且可用)?也有道理,但还是不太明白 什么方法更好(例如Qt混合了两种方法)。

我个人的决定 - 始终提供两种方式以获得更好的模块化(当我们有足够的依赖项时):

// a_decl.h
#include <iosfwd>

template< class T >
class QVector;

struct A
{
void process( QVector<int> );

void print( std::ostream& );
};

// a.h
// Include this file if you want to use most interface methods
// and don't want to write a lot of `#include`
#include <iostream>
#include <QVector>

#include "a_decl.h"

并让开发人员选择要包含的内容。

对于这些方法您有什么想说的?哪种方式更适合您?为什么?我们是否在所有情况下都有一个明显的赢家,还是总是取决于具体情况?

来自我与语言创建者的通信(我没有收到最终答案)

更新:

Boost 1.48.0 带来了容器库,它允许定义未定义用户类型的容器( 了解更多 )。

I think it's a spirit of C++ - you don't pay for what you don't
want ( you explicitly pay for what you need ):

// a.h
#include <iosfwd>

template< class T >
class QVector;

struct A
{
void process( QVector<int> );

void print( std::ostream& );
};

// some.cpp

#include "a.h"

#include <iostream> // I need only A::print() in this module, not full interface

...
A().print( std::cout );
...

That's why I think that it's not fair to prohibit developer to work
such way with STL ( Will C++11 STL have forward declaration's files? ).

But also I see one bad thing: dependencies of module A
will spread out in external context ( duplication of #include directives ) and it can lead to hard refactoring
when interface will change ( e.g. replace QVector with QList - and now you need to replace all occurrences of <QVector> with <QList> ).

Solution of this problem is:

#include <iostream>
#include <QVector>

struct A
{
void process( QVector<int> );

void print( std::ostream& );
};

Should we call this an idiom "fundamental types of interface" - module
interface's types should be like fundamentals types ( are always
defined and available )? It also makes sense, but still isn't clear
what way is better ( e.g. Qt mixes both approaches ).

My personal decision - always provide both ways for better modularity ( when we have enough dependencies ):

// a_decl.h
#include <iosfwd>

template< class T >
class QVector;

struct A
{
void process( QVector<int> );

void print( std::ostream& );
};

// a.h
// Include this file if you want to use most interface methods
// and don't want to write a lot of `#include`
#include <iostream>
#include <QVector>

#include "a_decl.h"

and let developer chooses what to include.

What you can say about these approaches? What way is better for you and why? Do we have a one clear winner for all cases or it always will depend on context?

From my correspondence with language creator ( I didn't receive an final answer )

UPDATE:

With boost 1.48.0 comes Container library, which allow to define containers of undefined user types ( read more ).

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

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

发布评论

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

评论(2

猥︴琐丶欲为 2024-12-21 03:12:47

C++ 是一种给程序员留下很多自由度的语言,因此不可避免地有不同的方法来做同一件事。

IMO,您定义的“解决方案”,即在任何 .h 文件中包含所有必要的包含或前向声明,是避免“不完整的头文件”的方法,我一直遵循这条规则。

有一本有趣的书,全面讨论了这样做或不这样做的所有利弊:John Lakos 的《Large-Scale C++ Software Design》,上面的规则就来自于此。

具体谈到前向声明,Lakos 区分了“仅在名称中”和“在大小中”类用法;只有在第二种情况下(根据他的意见)使用前向声明才是合法的:

定义:如果编译 f 的主体需要首先查看 T 的定义,则函数 f 使用类型 T 大小

定义:如果编译 f 以及 f 可能依赖的任何组件不需要首先查看 T 的定义,则函数 f 仅在名称上使用类型 T。

(来源

具体来说,Lakos 的推理围绕着以下含义:大规模系统(即具有一定复杂性的系统)的某些 C++ 编程风格,但我认为他的建议也非常适合任何规模的系统。

希望他的帮助。

C++ is a language that leaves many degrees of freedom to the programmer, so it is somehow unavoidable that there are different ways to do the same thing.

IMO, what you define as "the solution", i.e., including in any .h file all the necessary includes or forward declarations, is the way to go in order to avoid "incomplete header files", and I have always followed this rule.

There is an interesting book with a thorough discussion of all the pros and cons of doing or not doing so: "Large-Scale C++ Software Design" by John Lakos, where the rule above comes from.

Speaking specifically about forward declarations, Lakos distinguishes between "in-name-only" and "in-size" class usages; only in the second case it is legitimate (according to his opinion) the use of a forward declaration:

Definition: A function f uses a type T in size if compiling the body of f requires having first seen the definition of T.

Definition: A function f uses a type T in name only if compiling f and any of the components on which f may depend does not require having first seen the definition of T.

(source)

Specifically, Lakos' reasoning revolves around the implications of certain styles of programming C++ for large scale systems, i.e. system of certain complexity, but I think that his suggestions are very well suited for any-scale systems also.

Hope that his helps.

阳光下慵懒的猫 2024-12-21 03:12:47

我认为两者都是正确的方法。

创建前向标头很简单,并且是库维护者的主要责任。

然后,开发人员/客户可以根据他/她的需要选择使用前向标头或物理标头。

在可能的情况下,大型项目肯定会青睐并受益于前向标头。

包含物理标头并不能真正解决依赖性问题,但在许多情况下会引入新的依赖性。例如“如果我从这个标头中删除 #include ,那么某些编译将会失败(并且可能会因您正在使用的库及其平台差异而异)。

我认为一个好的库维护者< em>应该提供前向标头,因为其他人根据平台、版本等维护任何差异和更新是一件痛苦的事情。当项目变得足够大时,就会有一个明显的赢家 - 两者兼而有之,你总是有选择。

I consider both as the right approach.

Creating a forward header is simple, and much the responsibility of the library maintainer.

The developer/client can then choose to use the forward header or the physical header based on his/her needs.

Large projects will certainly favor and benefit from forward headers where possible.

Including the physical headers does not really solve dependency issues, but introduces new dependencies in many cases. e.g. "If I remove #include <string> from this header, then some compilations will fail (and could vary by the library you are using and its platform differences).

I think a good library maintainer should provide forward headers because it is a pain for others to maintain any differences and updates, based on platform, version, etc. There is a clear winner when projects become suitably large - with both, you always have choice.

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