C++头文件中应该放置using指令的位置是哪里?

14

我的项目中使用了一些相当复杂的数据结构,例如:

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 - Bathsheba
4
在源文件中,您无需重新定义类型,只需要记住FooTable的作用域在Bar类中。因此,您需要在Bar::create_foo()中指定,例如Bar::FooTable - Some programmer dude
3
如果你有一个命名空间,你基本上可以自由选择。个人而言,我会选择那些有意义的(例如,如果typedef专门用于某个类,则将其放在该类中;如果在没有与特定类相关联的其他地方使用,则放在命名空间中)。此外,你不需要“重新声明它”,你只需要使用Bar :: FooTable进行调用即可。 - Zoe stands with Ukraine
2
你还应该将函数定义为:auto Bar::create_foo() -> FooTable或类似的形式 :-) - Gojita
@LightnessRacesinOrbit 好吧,总得有人把它放在格式良好的答案中。在这种情况下,你做了这个工作,谢谢! :D - Max
显示剩余4条评论
3个回答

阿里云服务器只需要99元/年,新老用户同享,点击查看详情
11

然而这种方法的缺点是我需要在源文件中重新声明别名:

那是不正确的。你只需要将它设置为public,然后指定适当的作用域,这样在Bar的范围之外(包括返回类型,除非是尾随的!),你就可以称其为Bar::FooTable

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

(只需要在定义内部使用FooTable,因为它是一个成员!)

你的方法很好,不过我建议将所有内容都放在一个命名空间中。这样,无论你的别名是否在类中,它都是自包含的代码。这纯粹是一种风格问题,对其他人几乎没有影响。


4

但是现在我看到使用这种方式可能会有问题,因为它强制将此别名应用于包含此头文件的所有内容。

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

所以,我把using放在类内部

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

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

这是不必要的。

如果将别名设置为公共的,可以使用作用域解析运算符Bar::FooTable引用它。或者,您可以使用尾随返回类型,在此上下文中,名称在类的作用域内查找:

auto Bar::create_foo() -> FooTable

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

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

与放置其他任何声明的位置相同。至少放到您自己的命名空间中,但通常将声明放置在足够狭窄的范围内是一个不错的经验法则。如果类型别名仅与该类一起使用,则成员类型别名是很有意义的。


0

如评论中所提到的,您不需要重新声明它。如果您在类中声明了它,只需使用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。它本质上以类似于在全局命名空间中工作的方式公开typedef,但限制更多。更多信息请参见here


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