【问题标题】:Where to put using directives in C++ header files在 C++ 头文件中放置 using 指令的位置
【发布时间】:2020-01-05 11:41:41
【问题描述】:

对于我的项目,我使用了一些非常复杂的数据结构,例如

std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>

为了便于阅读,我想声明类型别名。我构建项目的代码已经通过在头文件中全局放置using 语句来做到这一点:

// bar.h
#ifndef BAR_H
#define BAR_H

#include <unordered_map>
#include <list>
#include <memory>
#include "foo.h"

using FooTable = std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>;

class Bar {
    FooTable create_foo();
};

#endif

由于我的 C++ 知识有点生疏,所以我只是采用了这种风格 - 但现在我读到以这种方式使用 using 可能会出现问题,因为它会在包含此标头的所有内容上强制使用此别名。

尽管进行了大量的谷歌搜索,但我找不到关于如何正确处理此问题的具体答案,只有很多关于不该做什么的陈述。所以,我只是把 using 放在类中:

// bar.h
#ifndef BAR_H
#define BAR_H

#include <unordered_map>
#include <list>
#include <memory>
#include "foo.h"


class Bar {
    using FooTable = std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>;

    FooTable create_foo();
};

#endif

但是这有一个缺点,我需要在源文件中重新声明别名:

// bar.cpp
#include "bar.h"

using FooTable = std::unordered_map<int, std::list<std::shared_ptr<const Foo>>>;

FooTable Bar::create_foo()
{
...
}

虽然这似乎可行,但我不确定这是否安全……我的直觉告诉我这有点难看。所以在我像这样重写我的整个项目之前,我想我会问:有没有更好/更优雅/更安全的方法来做到这一点?还是应该完全避免在头文件中使用类型别名?

【问题讨论】:

  • 我从不在全球范围内这样做;仅在类或命名空间内。好像这样污染更小。另外,由于我是老派,我倾向于使用typedef
  • 你不需要在源文件中重新定义类型,只需要记住FooTable的作用域在Bar类中。所以你需要指定,如Bar::FooTable Bar::create_foo()
  • @Someprogrammerdude 你应该对此做出回答。
  • 如果你有一个命名空间,你几乎可以选择。就我个人而言,我选择哪一个是有意义的(例如,如果 typedef 专门用于该类,我将它放在一个类中,如果它在其他地方使用而没有与特定类的连接,我将它放在命名空间中)。此外,您无需“重新声明”,只需使用Bar::FooTable 调用它即可。
  • 你还应该将你的函数定义为:auto Bar::create_foo() -> FooTable 或类似的东西:-)

标签: c++ scope using-directives type-alias


【解决方案1】:

但是这有一个缺点,我需要在源文件中重新声明别名:

这是不正确的。您只需将其设为public,然后指定适当的范围,因此您可以在Bar 的范围之外将其称为Bar::FooTable(包括返回类型,除非尾随!):

Bar::FooTable Bar::create_foo()
{ /* ... */ }

auto Bar::create_foo() -> FooTable
{ /* ... */ }

(只要FooTable 就可以定义内,因为它是成员!)

您的方法很好,尽管我也会将所有内容都放在名称空间中。那么你的别名是否在类中并不重要:它仍然在你自己的代码中自包含。这纯粹是一种风格问题,对其他人几乎没有影响。

【讨论】:

    【解决方案2】:

    但现在我读到以这种方式使用 using 可能会出现问题,因为它会在包含此标头的所有内容上强制使用此别名。

    这个问题同样适用于您定义的类Bar。所有包含此标头的程序都必须使用Bar 的定义。

    所以,我只是将 using 放在类中

    您减少了全局命名空间中的声明数量。这很好。

    但是这有一个缺点,我需要在源文件中重新声明别名

    这是不必要的。

    如果您将别名设为公开,则可以使用范围解析运算符将其引用为Bar::FooTable。或者您可以使用尾随返回类型,在该类型中在类的范围内查找上下文名称:

    auto Bar::create_foo() -> FooTable
    

    还有一个更通用的解决方案:命名空间。将您自己的所有声明放入单个命名空间(可以进一步划分为子命名空间)。这样您只需将一个名称引入全局命名空间,从而大大减少名称冲突的可能性。

    在 C++ 头文件中放置 using 指令的位置

    同样的推理适用于您放置任何其他声明的位置。至少在您自己的命名空间中,但通常将声明放在足够窄的范围内是一个不错的经验法则。如果类型别名只用于那个类,那么成员类型别名就很有意义。

    【讨论】:

      【解决方案3】:

      正如 cmets 中已经提到的,您不需要重新声明它。如果你在一个类中声明它,你只需要使用Bar::FooTable 来引用它。如果你在命名空间中声明它也是一样的,除非你在命名空间之外使用命名空间名称。如果你使用 typedef 也是一样的。

      是否在命名空间或类中声明它完全取决于您。就个人而言,我试图确保它具有尽可能相关的范围。例如,如果我有一个 typedef 仅用于与特定类有关,我将 typedef 放在类中。如果它具有与特定类无关的全局值,我在命名空间中声明它。

      话虽如此,我建议您不要在全局命名空间中声明它以避免歧义,如果您由于某种原因发现自己遇到命名冲突,如果您最终使用不同的 typedef(或者我认为一般情况下与您的 typedef/using 语句同名)在其他地方声明。

      此外,类中的 typedef 受访问修饰符的约束。默认情况下,它是私有的,这意味着您不能在课堂外使用它。如果您打算这样做,则需要将其公开。

      就安全性而言,在全局范围内声明它并不是特别安全,尤其是当您将它与 using namespace 结合使用时(这可能是其自身的问题 - 请参阅 this)。您可以在自己的命名空间中声明它(namespace Baz { using FooTable = blah; /* more code*/}),但将其声明为类会产生相同的效果。

      请注意,命名空间和类本质上是范围,它们有自己的动态。如果您在namespace Baz 内的源文件中编写代码,则可以访问在同一命名空间中声明的typedef,而无需指定Baz::FooTable。它本质上公开了 typedef,类似于它在全局命名空间中的工作方式,但以更受限制的方式。更多关于 here.

      【讨论】:

        猜你喜欢
        • 2010-10-04
        • 1970-01-01
        • 2013-05-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-07-10
        • 2010-10-13
        相关资源
        最近更新 更多