如果我有一个仅由纯虚析构函数组成的纯虚拟类InterfaceA
,为什么我必须将析构函数定义为inline
?当我尝试链接它时,我没有收到错误。
以下是一个公认的人为例子,但它说明了这一点。使用cmake和g ++不能为我编译这一点。但是,如果我按如下方式更改InterfaceA
析构函数定义 - inline InterfaceA::~InterfaceA(){};
则编译。
这是为什么? inline
关键字有什么作用?
// InterfaceA.h, include guards ommitted for clarity
class InterfaceA
{
public:
virtual ~InterfaceA() = 0;
};
InterfaceA::~InterfaceA(){};
// A.h, include guards ommitted for clarity
#include "InterfaceA.h"
class A : public InterfaceA
{
public:
A(int val)
: myVal(val){};
~A(){};
int myVal;
};
// AUser.h, include guards ommitted for clarity
#include "InterfaceA.h"
class AUser
{
public:
AUser(InterfaceA& anA)
: myA(anA){};
~AUser(){};
int getVal() const;
private:
InterfaceA& myA;
};
// AUser.cpp
#include "AUser.h"
#include "A.h"
int AUser::getVal() const
{
A& anA = static_cast<A&>(myA);
return anA.myVal;
}
// main.cpp
#include "AUser.h"
#include "A.h"
#include <iostream>
int main(){
A anA(1);
AUser user(anA);
std::cout << "value = " << user.getVal() << std::endl;
return 0;
}
答案 0 :(得分:1)
在头文件中定义函数时,必须使用inline
关键字。如果不这样做,并且该文件包含在多个翻译单元中,则该函数将被定义两次(或更多次)。
链接器错误可能类似于“符号......是多重定义的”对吗?
如果你在类的主体中定义了成员函数,它将隐式内联,它也可以工作。
请参阅this answer
要回答问题“内联关键字有什么作用?”:
在过去,它将用于询问编译器内联函数,即每当使用函数时插入代码而不是添加函数调用。最终它变成了一个简单的建议,因为编译器优化器变得更加了解哪些函数是内联候选者。目前,它几乎专门用于定义必须具有外部链接的头文件中的函数。
答案 1 :(得分:0)
inline
表示允许编译器直接将代码添加到调用函数的位置。它还从外部链接中删除了函数,因此你的编译单元都有本地版本的.. pure destructor。
// InterfaceA.h, include guards ommitted for clarity
class InterfaceA
{
public:
virtual ~InterfaceA() = 0;
};
你声明析构函数是虚拟的,所以编译器几乎永远不会使它内联。为什么?因为虚函数是通过vtable调用的 - 虚函数系统的内部工作,vtable很可能实现为成员函数的指针数组。如果函数是内联的,它将没有地址,没有合法的指针。如果尝试获取函数地址,则编译器会默默地忽略inline
关键字。另一个效果仍然存在:inline
d析构函数停止对链接器可见。
它可能看起来像声明纯虚拟析构函数看起来像矛盾,但事实并非如此。纯析构函数是一种在不引起UB的情况下始终被调用的析构函数。它的存在会使类抽象,但是析构函数调用的隐式调用仍然会发生。如果你没有声明析构函数体,它会导致UB,例如Windows上的purecall异常。
如果您不需要抽象基类,那么内联定义就足够了:
class InterfaceA
{
public:
virtual ~InterfaceA() {}
};
也被编译器视为inline
,但不允许混合内联定义和纯成员声明。