我想知道在头文件中声明和实现类之间的区别是什么,与在标头中对类进行类型化并在有效的.cpp文件中实现的常规方法相比。
为了更好地解释我在说什么,我的意思是正常方法之间的差异:
// File class.h
class MyClass
{
private:
//attributes
public:
void method1(...);
void method2(...);
...
};
//file class.cpp
#include "class.h"
void MyClass::method1(...)
{
//implementation
}
void MyClass::method2(...)
{
//implementation
}
和 just-header 方法:
// File class.h
class MyClass
{
private:
//attributes
public:
void method1(...)
{
//implementation
}
void method2(...)
{
//implementation
}
...
};
我可以得到主要区别:在第二种情况下,代码包含在每个需要它生成相同实现的更多实例的其他文件中,因此隐式冗余;而在第一种情况下,代码是自己编译的,然后引用MyClass
对象的每个调用都链接到class.cpp
中的实现。
但还有其他差异吗?取决于具体情况,使用方法而不是另一种方法更方便吗?我还读过某个地方,直接将方法体定义到头文件中是对编译器内联该方法的隐式请求,是真的吗?
答案 0 :(得分:36)
主要的实际区别在于,如果成员函数定义在标题的正文中,那么它们当然会为包含该标题的每个翻译单元编译一次。当您的项目包含几百或几千个源文件,并且相关的类被广泛使用时,这可能意味着大量的重复。即使每个类仅由2个或3个其他类使用,标题中的代码越多,工作量就越多。
如果成员函数定义在它们自己的转换单元(.cpp文件)中,则它们被编译一次,并且只有函数声明被多次编译。
确实,类定义中定义的成员函数(不仅仅是声明的)是隐式的inline
。但inline
并不意味着人们可能合理地猜测它意味着什么。 inline
表示函数的多个定义出现在不同的翻译单元中,然后链接在一起是合法的。如果类位于不同源文件将要使用的头文件中,则这是必要的,因此该语言会尝试提供帮助。
inline
也是编译器的一个暗示,该函数可以有用地内联,但尽管名称,这是可选的。编译器越复杂,就越能够自己做出关于内联的决定,并且对提示的需求就越少。比实际的内联标记更重要的是该函数是否完全可用于编译器。如果函数是在不同的转换单元中定义的,那么在编译对它的调用时它就不可用,所以如果有什么东西要内联调用,那么它必须是链接器,而不是编译器。 / p>
通过考虑第三种可能的方法,您可能会更好地看到差异:
// File class.h
class MyClass
{
private:
//attributes
public:
void method1(...);
void method2(...);
...
};
inline void MyClass::method1(...)
{
//implementation
}
inline void MyClass::method2(...)
{
//implementation
}
现在隐式内联已经不在了,这种“所有标题”方法和“标题加源”方法之间仍然存在一些差异。如何在翻译单元之间划分代码会对构建时发生的事情产生影响。
答案 1 :(得分:7)
对包含该实现的标头的任何更改都将强制包含该标头的所有其他类重新编译和重新链接。
由于标头的更改频率低于实现,因此通过将实现放在单独的文件中,可以节省大量的编译时间。
正如其他一些答案已经指出的那样,是的,在文件的class
块中定义方法将导致编译器内联。
答案 2 :(得分:7)
是的,编译器将尝试内联直接在头文件中声明的方法,如:
class A
{
public:
void method()
{
}
};
我可以考虑在分离头文件中的实现时遵循以下方便:
答案 3 :(得分:2)
是的,在类定义中定义方法等同于声明它们inline
。没有其他区别。在头文件中定义所有内容没有任何好处。
类似的东西通常在带有模板类的C ++中看到,因为模板成员定义也必须包含在头文件中(因为大多数编译器都不支持export
)。但是对于普通的非模板类,没有必要这样做,除非你真的想要将你的方法声明为inline
。
答案 4 :(得分:1)
对我来说,主要区别在于头文件就像是类的“接口”,告诉该类的客户端它的公共方法(它支持的操作)是什么,而客户不担心具体实现那些。从某种意义上说,它是一种从实现更改中封装其客户端的方法,因为只有cpp文件更改,因此编译时间要少得多。
答案 5 :(得分:0)
在过去,我创建了一个模块,可以屏蔽各种CORBA发行版中的差异,并且可以在各种OS /编译器/ CORBA库组合上统一工作。在头文件中实现它使得使用简单的include将其添加到项目更加容易。同样的技术保证了代码在调用它时需要重新编译的同时重新编译,即它是用不同的库或不同的OS编译的。
所以我的观点是,如果你有一个相当小的库,预计可以在各个项目中重复使用和重新编译,使它成为一个标题提供了与其他项目集成的优势,而不是向主项目添加额外的文件或重新编译一个外部的lib / obj文件。