避免在单个头文件中重新定义变量

6
我需要翻译的内容如下:

我的代码只需要一个头文件,这意味着不应该将声明和定义分割成单独的头文件和源文件。我已经正确地实现了它,并且对于我的用例,它可以按预期工作,其中此头文件仅适用于单个源文件。

现在,当它在多个源文件中使用时(其中有多个.cpp文件包含它),它将失败并出现链接器错误,指出某些变量正在被重新声明。那是因为我的代码像这样-

#ifndef HEADER_HPP
#define HEADER_HPP

....

std::streambuf const *R_coutbuf = std::cout.rdbuf();
std::streambuf const *R_cerrbuf = std::cerr.rdbuf();
std::streambuf const *R_clogbuf = std::clog.rdbuf();

void doSomething(){
    [uses if(R_coutbuf) and others]
}

....

#endif HEADER_HPP

现在最好的解决方案是在头文件中声明这些变量,并在单个cpp文件中定义/赋值它们,但正如我所说,我希望能够使用单个头文件完成此操作。这就带来了一个问题,如果多个源文件都包含它,就会出现重新声明的情况。

到目前为止,我还不确定该如何做,但我有两个想法 -

#ifdef DEFINE_VARIABLES
#define EXTERN /* nothing */
#else
#define EXTERN extern int
#endif /* DEFINE_VARIABLES */

EXTERN global_variable = something;

我对此并不十分确定,这样行得通吗?

我思考的第二种方式是将其放入匿名命名空间中,我正在尝试这种方式,目前编译成功 -

#ifndef HEADER_HPP
#define HEADER_HPP

....

namespace R {

    namespace {
        std::streambuf const *R_coutbuf = std::cout.rdbuf();
        std::streambuf const *R_cerrbuf = std::cerr.rdbuf();
        std::streambuf const *R_clogbuf = std::clog.rdbuf();
    }
    void doSomething(){
        [uses if(R_coutbuf) and others]
    }
}

....

#endif HEADER_HPP

有没有其他方法可以实现这个?我描述的两种方法有什么问题吗?

1
如果你不确定某件事情是否“可行”,那么直接尝试一下似乎比等待互联网上的某个人为你解决问题更快。 - Sam Varshavchik
1
我认为DEFINE_VARIABLES/EXTERN方法可以奏效,只要你在__包含_头文件.hpp_之前,在__确切地__一个_.cpp_文件中定义第一个宏。注意:将您的定义更改为#define EXTERN extern,因为您可能需要除了int之外的变量。 - CristiFati
@Hayt,我为什么要在这里使用“单例”?无论如何,“单例”已经包含了“静态”成员,因此只使用普通的“静态”变量会更好。 - Abhinav Gauniyal
静态成员和静态全局变量之间有一个区别。单例模式可以确保你只有一个对象实例在链接输出中,并且你不需要任何“保护”。当你在全局变量上使用静态修饰符时,你必须注意每个cpp(编译单元)都有自己的实例。因此,当你修改其中一个变量时,它不会传递到另一个cpp文件。 - Hayt
2
未命名的 namespace 将在每个翻译单元中生成1个副本。例如,如果您在未命名的 namespace 中声明了1个整数,则将为10个.cpp文件生成10个这样的整数。您是否打算这样做?最好使用函数的 static 来实现内部链接,如1个答案所建议的那样。 - iammilind
显示剩余8条评论
2个回答

5
你可以将变量设为函数内的本地静态变量:
```html

您可以将变量设置为函数内的本地静态变量:

```
inline std::streambuf const*& R_coutbuf() {
    static std::streambuf const* b = std::cout.rdbuf();
    return b;
}

这个会在第一次调用时实例化并每次返回相同的值,是吗?那我需要提供一个init()函数调用以初始化这些变量,对吧?并且这个函数只能在一个cpp中被调用一次,或者可以在我的头文件中被调用一次? - Abhinav Gauniyal
1
它将在第一次调用时实例化,是的。我不明白为什么你需要一个init()函数,初始化已经存在了。它不一定每次都返回相同的值。我有意地返回指针的引用,这样你就可以重新设置它。 - Chris Drew
我有点误解了,很高兴知道它是惰性初始化。 - Abhinav Gauniyal
好答案,我想抱怨一下这种情况语言不支持任何“内联变量”,但后来我发现c++17正好介绍了这个功能。 - Ap31

1

我描述的两种方法中有任何问题吗?

宏技巧

  • 优点
    • 在对象文件中产生最少量的变量。参见匿名命名空间的缺点。
  • 缺点
    • 非常脆弱。在包含头文件时,程序员必须知道是否有其他现有源文件包括变量定义。当源文件数量增加时,这不会很好地扩展。

内部链接(匿名命名空间)

  • 优点
    • 没有宏技巧的缺点。
  • 缺点
    • 包含头文件的每个对象文件都将拥有其自己的变量副本。我不确定,但这些变量是const类型的,并且在运行时都将具有相同的值,链接器可能能够丢弃重复项。无论如何,对于您的程序而言,差异仅值得几个字节。修改单个变量而不是修改共享变量将更改程序的含义。

为了比较,让我们考虑您已经排除的单独源文件选项。

  • 优点
    • 生成的目标文件中变量数量最少,类似于宏技巧。
    • 没有宏技巧的缺点。
  • 缺点
    • 无(除非您认为单独的源文件本身就是缺点)。

还有其他方法可以实现这个吗?

实际上没有。您可以使用static关键字而不是匿名命名空间来声明内部链接。


PS. 如果您在头文件中定义任何非模板函数,则必须声明为内联。您的匿名命名空间示例未能执行此操作。


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