C++ 中的内部 typedef - 好风格还是坏风格?

Posted

技术标签:

【中文标题】C++ 中的内部 typedef - 好风格还是坏风格?【英文标题】:Internal typedefs in C++ - good style or bad style? 【发布时间】:2010-10-20 01:25:19 【问题描述】:

我最近发现自己经常做的事情是声明与该类中的特定类相关的 typedef,即

class Lorem

    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
;

然后在代码的其他地方使用这些类型:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

我喜欢它的原因:

它减少了类模板引入的噪音,std::vector&lt;Lorem&gt; 变成了Lorem::vector,等等。 它用作意图声明 - 在上面的示例中,Lorem 类旨在通过 boost::shared_ptr 进行引用计数并存储在向量中。 它允许更改实现 - 即,如果 Lorem 需要在稍后阶段更改为侵入式引用计数(通过 boost::intrusive_ptr),那么这将对代码的影响最小。 我认为它看起来“更漂亮”并且可以说更容易阅读。

不喜欢的原因:

有时存在依赖问题 - 如果您想在另一个类中嵌入 Lorem::vector 但只需要(或想要)转发声明 Lorem(而不是引入对其头文件的依赖),那么您最终不得不使用显式类型(例如 boost::shared_ptr&lt;Lorem&gt; 而不是 Lorem::ptr),这有点不一致。 它可能不是很常见,因此更难理解?

我尝试对自己的编码风格保持客观,所以最好能获得一些其他的意见,这样我就可以稍微剖析一下我的想法。

【问题讨论】:

【参考方案1】:

我建议将这些 typedef 移到类之外。这样,您就可以消除对共享指针和向量类的直接依赖,并且仅在需要时才可以包含它们。除非您在类实现中使用这些类型,否则我认为它们不应该是内部类型定义。

你喜欢它的原因仍然匹配,因为它们是通过 typedef 的类型别名来解决的,而不是通过在你的类中声明它们。

【讨论】:

那会用 typedefs 污染匿名命名空间,不是吗?! typedef 的问题在于它隐藏了实际类型,当包含在多个模块中/被多个模块包含时,这可能会导致冲突,这些模块很难找到/修复。将它们包含在命名空间或类中是一个很好的做法。 名称冲突和匿名命名空间污染与将类型名保持在类内部或外部几乎没有关系。您可以与您的类发生名称冲突,而不是与您的 typedef。因此,为了避免名称污染,请使用命名空间。在命名空间中声明你的类和相关的 typedef。 将 typedef 放入类的另一个参数是模板化函数的使用。例如,当一个函数接收到包含未知字符串类型(字符串或您自己的字符串一致变体)的未知容器类型(向量或列表)时。确定容器有效负载类型的唯一方法是使用 typedef 'value_type',它是容器类定义的一部分。【参考方案2】:

我认为它的风格很棒,​​我自己也使用它。尽可能地限制名称的范围总是最好的,而使用类是在 C++ 中做到这一点的最佳方式。例如,C++ 标准库在类中大量使用 typedef。

【讨论】:

这是一个很好的观点,我想知道它看起来“更漂亮”是我的潜意识微妙地指出有限的范围是一件的事情。不过我想知道,STL 主要在类模板中使用它这一事实是否使它成为一种微妙的不同用法?在“具体”课程中证明其合理性是否更难? 标准库是由模板而不是类组成的,但我认为两者的理由是相同的。【参考方案3】:

当 typedef 仅在类本身中使用(即被声明为私有)时,我认为这是一个好主意。但是,由于您给出的确切原因,如果 typedef 需要在课堂之外知道,我不会使用它。在这种情况下,我建议将它们移到课堂外。

【讨论】:

【参考方案4】:

Typdefs 绝对是很好的风格。而且你所有的“我喜欢的理由”都是好的和正确的。

关于您遇到的问题。好吧,前向声明不是圣杯。您可以简单地设计代码以避免多级依赖。

你可以将 typedef 移到类之外,但是 Class::ptr 比 ClassPtr 漂亮得多,所以我不这样做。就像我的命名空间一样 - 事物在范围内保持连接。

有时我会这样做

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

它可以是所有域类的默认值,并且对某些域类进行一些专门化。

【讨论】:

【参考方案5】:

STL 一直在做这种事情——typedef 是 STL 中许多类的接口的一部分。

reference
iterator
size_type
value_type
etc...

都是类型定义,是各种 STL 模板类的接口的一部分。

【讨论】:

没错,我怀疑这是我第一次拿起它的地方。不过,这些似乎更容易证明是合理的?如果您碰巧沿着“元编程”路线思考,我不禁将类模板中的 typedef 视为更类似于变量。【参考方案6】:

Typedefs 是 policy based design and traits 在 C++ 中构建的,所以 C++ 中泛型编程的力量源于 typedefs 本身。

【讨论】:

【参考方案7】:

另一个投票赞成这是一个好主意。我在编写一个在时间和空间上都必须高效的模拟时开始这样做。所有的值类型都有一个 Ptr typedef,它最初是一个 boost 共享指针。然后我进行了一些分析并将其中一些更改为 boost 侵入式指针,而无需更改使用这些对象的任何代码。

请注意,这仅在您知道将在何处使用类并且所有用途都具有相同要求时才有效。例如,我不会在库代码中使用它,因为您在编写库时无法知道将在其中使用它的上下文。

【讨论】:

【参考方案8】:

它用作意向声明 - 在上面的示例中,Lorem 类 旨在被引用计数 通过 boost::shared_ptr 并存储在 向量。

这正是它所做的。

如果我在代码中看到 'Foo::Ptr',我完全不知道它是 shared_ptr 还是 Foo*(STL 的 ::pointer typedef 是 T*,请记住)或其他什么。 尤其是如果是共享指针,我根本不提供 typedef,而是在代码中明确使用 shared_ptr。

实际上,我几乎从不在模板元编程之外使用 typedef。

STL 一直在做这种事情

具有根据成员函数和嵌套 typedef 定义概念的 STL 设计是一个历史性的死胡同,现代模板库使用免费函数和特征类(参见 Boost.Graph),因为这些不排除内置从对概念进行建模的类型中提取类型,并且因为它可以更容易地调整未根据给定模板库的概念设计的类型。

不要以 STL 作为犯同样错误的理由。

【讨论】:

我同意你的第一部分,但你最近的编辑有点短视。这种嵌套类型简化了 trait 类的定义,因为它们提供了合理的默认值。考虑一下新的 std::allocator_traits&lt;Alloc&gt; 类...您不必为您编写的每个分配器专门化它,因为它只是直接从 Alloc 借用类型。 @Dennis:在 C++ 中,便利应该在库的 /user/ 一侧,而不是 /author/ 一侧:用户希望为 trait 提供统一的接口,并且由于上面给出的原因,只有一个特征类可以给出它)。但即使作为Alloc 作者,为他的新类型专门化std::allocator_traits&lt;&gt; 并不比添加所需的typedef 更难。我还编辑了答案,因为我的完整回复不适合评论。 但它站在用户一边。作为allocator_traits 尝试创建自定义分配器的用户,我不必为traits 类的15 个成员操心......我所要做的就是说typedef Blah value_type; 和提供适当的成员函数,默认的allocator_traits 将计算出其余的。此外,请查看您的 Boost.Graph 示例。是的,它大量使用了traits类......但是graph_traits&lt;G&gt;的默认实现只是简单地查询G以获取它自己的内部类型定义。 甚至 03 标准库也在适当的地方使用了特征类……该库的理念不是对容器进行通用操作,而是对迭代器进行操作。因此它提供了一个iterator_traits 类,以便您的通用算法可以轻松查询适当的信息。同样,默认情况下查询迭代器以获取其自己的信息。总而言之,特征和内部 typedef 几乎不是相互排斥的……它们相互支持。 @Dennis: iterator_traits 变得很有必要,因为T* 应该是RandomAccessIterator 的模型,但是您不能将所需的类型定义放入T*。一旦我们有了iterator_traits,嵌套的 typedef 就变得多余了,我希望它们已经在那里被删除了。出于同样的原因(无法添加内部类型定义),T[N] 没有模拟 STL Sequence 概念,您需要诸如 std::array&lt;T,N&gt; 之类的杂项。 Boost.Range 展示了如何定义现代序列概念,T[N] 可以建模,因为它不需要嵌套的 typedef,也不需要成员函数。【参考方案9】:

目前我正在编写代码,它大量使用这些类型定义。到目前为止还不错。

但是我注意到经常有迭代的 typedef,定义被分成几个类,你永远不知道你在处理什么类型。我的任务是总结隐藏在这些 typedef 后面的一些复杂数据结构的大小——所以我不能依赖现有的接口。结合三到六级嵌套命名空间,就会变得混乱。

所以在使用它们之前,有几点需要考虑

还有其他人需要这些类型定义吗?该类是否被其他类大量使用? 我是缩短使用时间还是隐藏课程? (在隐藏的情况下,您也可以考虑接口。) 其他人是否在使用该代码?他们是怎么做到的呢?他们会认为这更容易还是会感到困惑?

【讨论】:

以上是关于C++ 中的内部 typedef - 好风格还是坏风格?的主要内容,如果未能解决你的问题,请参考以下文章

VARCHAR 作为数据库中的外键/主键好还是坏?

从 C 函数返回指针是好还是坏设计? [关闭]

谷歌风格C++命名规则

谷歌风格C++命名规则

Typedef 到 C++ 中的周围类型

如何解决 C++ 中的 typedef 重定义?