【问题标题】:multiple definition error including c++ header file with inline code from multiple sources多个定义错误,包括来自多个来源的内联代码的 c++ 头文件
【发布时间】:2008-10-17 12:57:19
【问题描述】:

我有一个包含类的 c++ 头文件。 我想在几个项目中使用这个类,但我不想为它创建一个单独的库,所以我将方法声明和定义都放在头文件中:

// example.h
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
namespace test_ns{

class TestClass{
public:
    void testMethod();
};

void TestClass::testMethod(){
    // some code here...
}

} // end namespace test_ns
#endif

如果在同一个项目中我从多个 cpp 文件中包含此标头,我会收到一条错误消息“multiple definition of test_ns::TestClass::testMethod()”,而如果我将方法定义放在类主体中,则不会发生这种情况:

// example.h
#ifndef EXAMPLE_H_
#define EXAMPLE_H_
namespace test_ns{

class TestClass{
public:
    void testMethod(){
        // some code here...
    }
};

} // end namespace test_ns
#endif

既然类是在命名空间中定义的,那么这两种形式不应该是等价的吗?为什么在第一种情况下认为方法被定义了两次?

【问题讨论】:

    标签: c++


    【解决方案1】:

    这些不是等价的。给出的第二个示例在方法上具有隐式的“内联”修饰符,因此编译器将自行协调多个定义(如果方法不可内联,则很可能与方法的内部链接)。

    第一个示例不是内联的,因此如果此标头包含在多个翻译单元中,那么您将有多个定义和链接器错误。

    此外,标题应该始终受到保护,以防止同一翻译单元中出现多个定义错误。那应该将您的标题转换为:

    #ifndef EXAMPLE_H
    #define EXAMPLE_H
    
    //define your class here
    
    #endif
    

    【讨论】:

    • 感谢您指出这一点...我忘记了示例中的包含守卫(但实际代码中没有)。
    【解决方案2】:

    类体内部被编译器认为是内联的。 如果您在正文之外实现,但仍在标头中,则必须将方法显式标记为“内联”。

    namespace test_ns{
    
    class TestClass{
    public:
        inline void testMethod();
    };
    
    void TestClass::testMethod(){
        // some code here...
    }
    
    } // end namespace test_ns
    

    编辑

    对于我自己来说,通过意识到编译器看不到任何像头文件这样的东西,这通常有助于解决这些类型的编译问题。头文件经过预处理,编译器只看到一个巨大的文件,其中包含每个(递归)包含文件中的每一行。通常,这些递归包含的起点是正在编译的 cpp 源文件。 在我们公司,即使是一个看起来不起眼的 cpp 文件也可以作为一个 300000 行的怪物呈现给编译器。

    因此,当一个未声明为内联的方法在头文件中实现时,编译器最终可能会在预处理文件中多次看到 void TestClass::testMethod() {...}。现在您可以看到这没有任何意义,与在一个源文件中多次复制/粘贴时获得的效果相同。 即使您在每个编译单元中只使用一次成功,通过某种形式的条件编译(例如使用包含括号),链接器仍然会发现此方法的符号位于多个编译单元(目标文件)中。

    【讨论】:

      【解决方案3】:

      不要将函数/方法定义放在头文件中,除非它们被内联(通过直接在类声明中定义或由 inline 关键字明确指定)

      头文件(大部分)用于声明(无论您需要声明什么)。允许的定义是常量和内联函数/方法(以及模板)的定义。

      【讨论】:

        【解决方案4】:

        实际上,可以在单个头文件中定义定义(无需单独的 .c/.cpp 文件),并且仍然可以从多个源文件中使用它。

        考虑这个foobar.h 标头:

        #ifndef FOOBAR_H
        #define FOOBAR_H
        
        /* write declarations normally */
        void foo();
        void bar();
        
        /* use conditional compilation to disable definitions when necessary */
        #ifndef ONLY_DECLARATIONS
        void foo() {
           /* your code goes here */
        }
        void bar() {
           /* your code goes here */
        }
        #endif /* ONLY_DECLARATIONS */
        #endif /* FOOBAR_H */
        

        如果您只在一个源文件中使用此标头,请正常包含并使用它。 比如main.c

        #include "foobar.h"
        
        int main(int argc, char *argv[]) {
            foo();
        }
        

        如果您的项目中有其他源文件需要foobar.h,则在包含它之前使用#define ONLY_DECLARATIONS 宏。 在use_bar.c 你可以写:

        #define ONLY_DECLARATIONS
        #include "foobar.h"
        
        void use_bar() {
            bar();
        }
        

        编译后 use_bar.o 和 main.o 可以毫无错误地链接在一起,因为其中只有一个 (main.o) 将实现 foo() 和 bar()。

        这有点不习惯,但它允许将定义和声明放在一个文件中。我觉得这是一个穷人替代真正的modules

        【讨论】:

          【解决方案5】:

          您的第一个代码 sn-p 违反了 C++ 的“一个定义规则”- see here for a link to a Wikipedia article describing ODR.您实际上违反了第 2 点,因为每次编译器将头文件包含到源文件中时,您都会遇到编译器生成test_ns::TestClass::testMethod() 的全局可见定义的风险。当然,当您开始链接代码时,链接器将拥有小猫,因为它会在多个目标文件中找到相同的符号。

          第二个 sn-p 有效,因为您已经内联了函数的定义,这意味着即使编译器没有为函数生成任何内联代码(例如,您关闭了内联或编译器决定函数太大而无法内联),为函数定义生成的代码将仅在翻译单元中可见,就好像您将其卡在匿名命名空间中一样。因此,您会在生成的目标代码中获得该函数的多个副本,链接器可能会或可能不会优化,具体取决于它的智能程度。

          您可以在您的第一个代码 sn-p 中通过在 TestClass::testMethod() 前加上 inline 来实现类似的效果。

          【讨论】:

            【解决方案6】:
            //Baseclass.h  or  .cpp
            
            #ifndef CDerivedclass
            #include "Derivedclass.h"
            #endif
            
            or
            //COthercls.h    or .cpp
            
            #ifndef CCommonheadercls
            #include "Commonheadercls.h"
            #endif
            
            I think this suffice all instances.
            

            【讨论】:

              猜你喜欢
              • 2014-01-08
              • 2012-10-29
              • 2015-07-04
              • 1970-01-01
              • 2014-08-28
              • 2011-11-22
              • 1970-01-01
              • 2011-11-14
              • 1970-01-01
              相关资源
              最近更新 更多