我将Base类更改为抽象(即我将其中一个方法设为纯虚拟)并重新编译它。当我将它与派生类链接时,链接器抱怨了vtable。我用nm调查了一些事情,但我不确定nm告诉我的是什么。我只是通过删除* .o文件并重新编译Derived类来解决问题,但我想了解这里的vtable究竟发生了什么。
我的原始代码是:
Base.h
class Base {
public:
virtual void doSomething();
};
Base.cpp
#include <iostream>
#include <Base.h>
void Base::doSomething() {
std::cout << "Base::doSomething()" << "\n";
}
Derived.h
#include <Base.h>
class Derived : public Base {
public:
Derived();
void doSomething() override;
};
Derived.cpp
#include <iostream>
#include <Derived.h>
Derived::Derived() {
std::cout << "Derived::Derived() constructor" << "\n";
}
void Derived::doSomething() {
std::cout << "Derived::doSomething()" << "\n";
}
Makefile包含以下内容:
CXXFLAGS = -std=c++14 -pedantic -Wall -Werror -I ./
default: build
clean:
rm -f proggy
rm -f *.o
proggy: proggy.o Base.o Derived.o
g++ -o proggy proggy.o Base.o Derived.o
Base.o: Base.cpp Base.h
Derived.o: Derived.cpp Derived.h
然后我跑了make,一切都很顺利。为了记录,我在这一点上也运行了nm,如下所示:
$ nm -C Derived.o | grep Base
00000000 W Base::Base()
00000000 W Base::Base()
00000000 n Base::Base()
U typeinfo for Base
U vtable for Base
我看到 typeinfo for Base 未定义,但这一切看起来都很开心。
此外,在Base.o中,现阶段还有这个:
nm -C Base.o | grep Base
000000d4 t _GLOBAL__sub_I__ZN4Base11doSomethingEv
00000000 T Base::doSomething()
00000044 R typeinfo for Base
0000004c R typeinfo name for Base
00000038 R vtable for Base
然后我按如下方式更改了Base.h和Base.cpp以使类抽象:
Base.h
class Base {
public:
virtual void doSomething() = 0; // pure virtual
};
Base.cpp
#include <iostream>
#include <Base.h>
// void Base::doSomething() {
// std::cout << "Base::doSomething()" << "\n";
// }
然后当我跑步时,我收到了这个错误:
$ make proggy
g++ -std=c++14 -pedantic -Wall -Werror -I ./ -c -o Base.o Base.cpp
g++ -o proggy proggy.o Base.o Derived.o
Derived.o:(.rodata+0x5c): undefined reference to `typeinfo for Base'
Derived.o: In function `Base::Base()':
Derived.cpp:(.text._ZN4BaseC2Ev[_ZN4BaseC5Ev]+0x48): undefined reference to `vtable for Base'
collect2: error: ld returned 1 exit status
Makefile:17: recipe for target 'proggy' failed
make: *** [proggy] Error 1
其中c ++ filt告诉我以下内容:
$ c++filt _ZN4BaseC2Ev
Base::Base()
然后我按照以下方式运行nm,它告诉我:
$ nm -C Derived.o | grep Base
00000000 W Base::Base()
00000000 W Base::Base()
00000000 n Base::Base()
U typeinfo for Base
U vtable for Base
我可以看到 typeinfo for Base 是未定义的,但它也是一开始的,所以我认为这不是一个问题,但确实如此。另请注意,现在没有在Base.o中提及Base。
$ nm -C Base.o | grep Base
即。这个nm和grep命令找不到任何东西。
最后,我删除了所有* .o文件并再次运行make,一切都很好。然后我运行nm来查看nm报告,并报告以下内容:
$ nm -C Derived.o | grep Base
00000000 W Base::Base()
00000000 W Base::Base()
00000000 n Base::Base()
00000000 V typeinfo for Base
00000000 V typeinfo name for Base
00000000 V vtable for Base
这一切意味着什么?
我的问题是:
答案 0 :(得分:6)
从日志中
$ make proggy
g++ -std=c++14 -pedantic -Wall -Werror -I ./ -c -o Base.o Base.cpp
g++ -o proggy proggy.o Base.o Derived.o
这意味着您的makefile不会重新构建Derived.o
,只会重新构建Base.o
。
这当然会引发这个问题。
您需要修正Makefile
,以便为Derive.cpp
Base.h
添加适当的依赖关系。
答案 1 :(得分:3)
vtable存储已实现的虚拟方法的地址。如果类的所有方法都是纯虚拟的并且没有实现,那么就不需要生成vtable *,因为没有办法自己实例化这样的类(在调试模式下,仍然可以生成vtable,指向所有陷阱功能)。
当您使用Derived.cpp
编译Base.h
具有非纯虚函数时,它会引用Base
的vtable。
当您随后将Base.h
更改为仅包含纯虚函数并重建Base.o
时,来自Base.o
的vtable消失。此时您需要重建Derived.o
,否则它将继续引用不存在的vtable。
重建Derived.o
时,编译器会发现Base
是纯虚拟类,并在Derived.o
本身为它生成vtable,因为它知道Base.o
中没有{ {1}}。
在基类中重新排序虚拟函数后,会出现另一个潜在问题。然后派生类,如果不重建,最终可能会在其父类中调用错误的函数。
这就是为什么重要的是让依赖链正确,以确保在必要时重建依赖对象文件。
Derived.o: Derived.cpp Derived.h Base.h
*血腥细节依赖于编译器,但GCC的做法是:由于无法实例化纯虚拟类,因此vtable生成实际上是推迟,直到至少有一个实现,因为只有这样才能有一个类的实例。因此,vtable是使用每个派生实现生成的,并导出为“弱”对象(类型V
),以允许在链接时合并潜在的重复项。