2

在下面的代码中,foo应该是任何人都可以访问的函数,但foo_helper不应该,这就是我将它放在匿名命名空间中的原因。显然我在这个例子中省略了包括警卫和包括,但他们在那里。

foo.h

namespace
{
    void foo_helper() {}

    template <typename T, typename... Tail>
    void foo_helper(T head, Tail... tail)
    {
        bar(head);
        foo_helper(tail...);
    }
}

void foo();

template <typename... Args>
void foo(Args... args)
{
    before();
    foo_helper(args...);
    after();
}

foo.cpp

void foo() {}

问题是,为了让foo_helper的可变参数模板工作,它需要有没有参数的初始版本。但是,这迫使我定义一个非模板函数是一个头文件,在将这个文件包含在多个源文件中后会中断。我无法将 的定义移动foo_helper到源文件,因为它位于匿名命名空间中,因为它不应该是可访问的。

有没有办法解决这个问题?

4

2 回答 2

3
inline void foo_helper() {};

解决你的问题。

inline主要意味着“将丢弃此函数的冲突定义,并保留其中一个版本”。

它还以模糊的方式非约束性地暗示“内联”(因为该标准并未真正涵盖内联是什么)。编译器可能会或可能不会注意该建议。

请注意,匿名命名空间不会“使其无法使用”或其他任何东西。匿名命名空间旨在阻止链接器冲突,仅此而已。创建一个名为detailsand 的命名空间......好吧,相信用户不要去戳里面。

在标头中使用匿名名称空间是一个非常糟糕的主意

如果inline另一个头文件中有一个函数(或模板函数)访问匿名命名空间中的一个符号或函数,那么您几乎肯定会违反 ODR(一个定义规则)。那就是同一个对象、函数等有两个不同的定义,这是不允许的。

例如:

inline void bob() {
  foo(1,2,3);
}

如果这#include在两个不同的 .cpp 文件中是 d,那么您只是制作了一个格式错误的程序(不需要诊断)。

通常这种格式不正确的程序“表现得如你所愿”,但有时它们并非如此。例如,如果您在某处得到一个static局部变量,其存在取决于 ODR 违规,您可以让多个编译单元不同意哪个存在以及它的属性是什么。

在更一般的意义上,程序的链接顺序可能会改变其行为,因为“选择”了不同的定义(可能存在极其细微的差异)。或者月相也可以这样做。

ODR 违规行为出人意料地是良性的,直到它们用难以追踪的非本地错误来折磨你。

于 2015-10-15T17:23:23.347 回答
1

我先说一句:在这里使用匿名命名空间不符合您的目的。由于您在头文件中定义它,因此它根本不受保护:它仍然在包含您的头文件的任何文件的范围内。此外,由于您已在匿名命名空间中定义了它,因此每个使用它的翻译单元都会发出该函数的单独副本,并且链接器无法折叠它们。如果您真的希望它是私有的,那么这些天我并不是最好的 C++ 风格,所以也许其他人会纠正我,但我倾向于使用私有命名空间:

namespace my_stuff {
    void foo_helper();
}

void foo() {
    my_stuff::foo_helper();
}

正如 Yakk 所指出的,您可以使用内联函数,这将允许编译器将定义合并为一个。在现代实践中,实际上不应该有其他理由避免使用该inline关键字,因为现在编译器将自行决定是否内联函数,而不是听取您给出的提示。

由于您已经在匿名命名空间中定义了该函数,正如我上面提到的,如果您保留它,您实际上不需要做任何其他事情来避免链接器错误。这种方法的缺点是您将foo_helper()在每个翻译单元中拥有单独的副本,并且链接器无法合并这些副本。

您还可以做其他体操,主要涉及sizeof...,但我认为这些都不是理想的。

于 2015-10-15T17:38:39.977 回答