typedefs的头文件最佳实践
我在一个项目中广泛地使用了shared_ptr和STL,这导致了过长的,容易出错的types,比如shared_ptr< vector< shared_ptr<const Foo> > >
(我是一个ObjC程序员,是相当普遍的,而且这样做太多了。)我相信,要一致地调用这个FooListPtr
并logging命名约定“Ptr”意思是shared_ptr,而“List”意思是shared_ptr的向量。
这很容易typedef,但它引起头痛头。 我似乎有几个选项来定义FooListPtr
:
- Foo.h. 这纠缠所有标题和创build严重的构build问题,所以这是一个不起动。
- FooFwd.h(“forward header”)。 基于iosfwd.h,这就是Effective C ++所build议的。 这是非常一致的,但维护两倍的标题数量的开销看起来很烦人。
- Common.h(将所有这些文件放在一个文件中)。 这通过缠绕许多不相关的types来杀死可重用性。 您现在不能只拾取一个对象并将其移动到另一个项目。 这是一个不起眼的人。
- 如果还没有被typedefed的话,某种特殊的#define魔法。 我有一个持久的不喜欢的预处理器,因为我认为这使得新手难以挖掘的代码,但也许….
- 使用vector子类而不是typedef。 这似乎很危险
这里有最佳做法吗? 它们如何以真实的代码出现,重用性,可读性和一致性是至关重要的?
我已经标记了这个社区维基,如果别人想要添加更多的讨论选项。
我正在编写一个听起来像使用common.h
方法的项目。 这个项目非常好。
有一个名为ForwardsDecl.h
的文件,它位于预编译的头文件中,并简单地向前声明所有重要的类和必要的typedef。 在这种情况下,使用unique_ptr
而不是shared_ptr
,但是使用应该类似。 它看起来像这样:
// Forward declarations class ObjectA; class ObjectB; class ObjectC; // List typedefs typedef std::vector<std::unique_ptr<ObjectA>> ObjectAList; typedef std::vector<std::unique_ptr<ObjectB>> ObjectBList; typedef std::vector<std::unique_ptr<ObjectC>> ObjectCList;
即使这些类只是前向声明(完整的类定义不是必须的,因此不需要包含每个类的头文件),Visual C ++ 2010仍然接受此代码。 我不知道这是否是标准的,其他编译器将需要完整的类定义,但是它不会有用:另一个类(ObjectD)可以将ObjectAList作为成员,而不需要包含ObjectA.h,真的有助于减less头文件的依赖关系!
维护不是一个特别的问题,因为转发声明只需要写一次,并且任何后续的更改只需要在类头文件中的完整声明中进行(这样会减less由于减less的源文件而重新编译依赖)。
最后,看起来这可以在项目之间共享(我还没有自己试过),因为即使一个项目没有实际声明一个ObjectA,也没关系,因为它只是被转发声明的,如果你不使用它,编译器不在乎。 因此,该文件可以包含所有项目中使用的类的名称,如果某个项目缺less某些项目,则无关紧要。 所有需要的是必要的完整声明头文件(例如ObjectA.h
)包含在实际使用它们的任何源文件(.cpp)中。
我将采用前向头文件和一种特定于您的项目的common.h
头文件的组合方式,并且只包含所有前向声明头文件和任何其他常见和轻量级的东西。
你抱怨维护两倍数量的标题的开销,但我不认为这应该是一个太多的问题:向前的标题通常只需要知道非常有限的types(一个?),有时甚至不是完整的types。
如果真的有这么多的头文件,你甚至可以尝试使用脚本自动生成头文件(例如在SeqAn中完成)。
+1用于loggingtypedef约定。
- Foo.h – 你能详细描述一下你的问题吗?
- FooFwd.h – 我不会一般地使用它们,只能在“明显的热点”上使用它们。 (是的,“热点”很难确定)。 它不会更改规则IMO,因为当您引入一个fwd头时,foo.h中的关联typedef会移到那里。
- Common.h – 对于小项目很酷,但没有规模,我同意。
- 一些花哨的#定义 …请不要!
- 使用一个vector子类 – 并没有让它变得更好。 不过,你可能会使用遏制。
因此,这里的prelimenarybuild议(从其他问题修改..)
-
标准types头文件
<boost/shared_ptr.hpp>
,<vector>
等可以进入项目的预编译头文件/共享包含文件。 这并不坏。 (我个人在需要的时候仍然包括它们,但是除了把它们放入PCH之外,它还是有效的。) -
如果容器是实现细节,那么typedef就是声明容器的地方(例如,如果容器是私有类成员,则是私有类成员)
-
如果关联的types是该types的主要用途, 则关联的types(如
FooListPtr
)将转至Foo所在的位置。 对于某些types来说几乎总是如此 – 例如shared_ptr
。 -
如果
Foo
获得一个单独的前向声明头文件,并且关联的types与该头文件正确,那么它也会移动到FooFwd.h中。 -
如果这个types只与一个特定的接口(例如公共方法的参数)相关联,它就会到达那里。
-
如果types是共享的(并且不符合任何以前的标准),它将获得它自己的标题。 请注意,这也意味着拉入所有的依赖关系。
这对我来说感觉“显而易见”,但我同意这不是一个好的编码标准。
我在一个项目中广泛地使用了shared_ptr和STL,这导致了像shared_ptr <vector <shared_ptr>>这样的过长且容易出错的types(我是一个ObjC程序员,偏好的地方是长名字,而且这样做太多了。)我相信,要一致地调用这个FooListPtr并logging命名约定“Ptr”意思是shared_ptr而“List”意思是shared_ptr的向量将会更清楚。
对于初学者,我build议使用优秀的devise结构进行范围界定(例如,命名空间)以及typedefs的描述性非缩写名称。 FooListPtr
非常短, FooListPtr
。 没有人想猜测缩写意味着什么(或者惊讶地发现Foo是const,共享等等),并且没有人愿意仅仅因为范围冲突而改变他们的代码。
它也可能有助于为你的库中的types定义(以及其他常见的类别)select一个前缀。
将types拖出声明的范围也是一个糟糕的主意:
namespace MON { namespace Diddy { class Foo; } /* << Diddy */ /*...*/ typedef Diddy::Foo Diddy_Foo; } /* << MON */
有这样的例外:
- 一个完全ecapsulated的私人types
- 一个新的范围内的一个包含的types
而我们在这个时候,应该避免在命名空间范围和命名空间别名中使用 – 如果你想最小化未来的主要内容,就限定范围。
这很容易typedef,但它引起头痛头。 我似乎有几个选项来定义FooListPtr:
Foo.h. 这纠缠所有标题和创build严重的构build问题,所以这是一个不起动。
它可能是真正依赖于其他声明的声明的选项。 意味着你需要划分包,或者有一个用于子系统的通用本地化界面。
FooFwd.h(“forward header”)。 基于iosfwd.h,这就是Effective C ++所build议的。 这是非常一致的,但维护两倍的标题数量的开销看起来很烦人。
不要担心这个的维护,真的。 这是一个很好的做法。 编译器使用前向声明和typedef很less费力。 这不是烦人的,因为它有助于减less依赖关系,并有助于确保它们都是正确和可见的。 实际上没有更多的维护,因为其他文件引用“包types”标题。
Common.h(将所有这些文件放在一个文件中)。 这通过缠绕许多不相关的types来杀死可重用性。 您现在不能只拾取一个对象并将其移动到另一个项目。 这是一个不起眼的人。
基于软件包的依赖和包含是非常好的(理想的,真的) – 不要排除这一点。 你显然必须创build一个devise和结构良好的包接口(或库),并且代表相关的组件类。 你正在做一个不必要的问题出于对象/组件重用。 尽量减less库的静态数据,让链接和条带阶段完成他们的工作。 再次,保持你的软件包小,可重用,这不会是一个问题(假设你的库/包devise得很好)。
如果还没有被typedefed的话,某种特殊的#define魔法。 我有一个持久的不喜欢的预处理器,因为我认为这使得新手难以挖掘的代码,但也许….
实际上,你可以多次在相同的范围声明一个typedef(例如,在两个单独的头文件中) – 这不是一个错误。
在不同的基础types的相同范围内声明typedef 是一个错误。 明显。 你必须避免这一点,幸运的是编译器强制执行。
为了避免这种情况,创build一个包含世界的“翻译版本” – 编译器将标记不匹配的typedeffedtypes的声明。
试图用最less的typedef和/或forwards(它们足够接近以便在编译时免费)潜行是不值得的。 有时你需要大量的前向声明的条件支持 – 一旦定义了,就很容易了(stl库就是一个很好的例子 – 如果你也是向前声明template<typename,typename>class vector;
)。
最好是让所有这些声明立即可见,以避免任何错误,并且在这种情况下可以避免预处理器作为奖励。
使用vector子类而不是typedef。 这似乎很危险
std::vector
的子类通常被标记为“初学者的错误”。 这个容器不是要被分类的。 不要仅仅为了减less编译时间/依赖就采取不好的做法。 如果依赖关系真的很重要,那么你应该使用PIMPL,反正:
// <package>.types.hpp namespace MON { class FooListPtr; } // FooListPtr.hpp namespace MON { class FooListPtr { /* ... */ private: shared_ptr< vector< shared_ptr<const Foo> > > d_data; }; }
这里有最佳做法吗? 它们如何以真实的代码出现,重用性,可读性和一致性是至关重要的?
最终,我发现了一个简洁的基于软件包的方法,可以最大限度地重复使用,减less编译时间,并最大限度地减less依赖性。
不幸的是,使用typedefs时,您必须select不理想的头文件选项。 有一些特殊情况,其中选项一(正确的类头)运作良好,但它听起来像它不会为你工作。 也有最后一个选项可以正常工作的情况,但是通常是在你使用子类来replace涉及一个类的模式的情况下,使用std :: vectortypes的一个成员。 对于你的情况,我会使用正向声明头解决scheme。 有额外的打字和开销,但它不会是C ++,否则,对不对? 它使事情分离,清洁和快速。