有这个代码:
#include <iostream>
class Base
{
int x;
};
class Derived : virtual public Base
{
int y;
};
int main()
{
std::cout << sizeof(Derived) << std::endl; // prints 12
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我已经读过,当某个类被虚拟继承时,会为类派生创建空虚表,因此内存布局如下:
Derived::ptr to empty vtable
Derived::y
Base::x
Run Code Online (Sandbox Code Playgroud)
它是 12 个字节。问题是 -如果没有任何虚拟方法,这个空的vtable 的目的是什么,它是如何使用的?
在以下问题中,一个答案建议对象的动态类型不能更改:所引用对象的动态类型何时可以更改?
但是,我听说在CPPCon或其他会议上的某些发言者并非如此。
确实这似乎并不正确,因为在以下示例的每次循环迭代中,GCC和Clang都重新读取了vtable指针:
class A {
public:
virtual int GetVal() const = 0;
};
int f(const A& a){
int sum = 0;
for (int i = 0; i < 10; ++i) {
// re-reads vtable pointer for every new call to GetVal
sum += a.GetVal();
}
return sum;
}
Run Code Online (Sandbox Code Playgroud)
但是,如果添加以下内容:
class B final : public A {
public:
int GetVal() const override {
return 1;
}
};
int g(const B& b){
int sum = 0;
for (int i …Run Code Online (Sandbox Code Playgroud) 在这里,在这段代码中,ob1的大小为16,这很好(由于虚拟指针),但我不明白为什么ob2的大小为24。
#include <iostream>
using namespace std;
class A {
int x;
};
class B {
int y, z;
};
class C : virtual public A {
int a;
};
class D : virtual public B {
int b;
};
int main() {
C ob1;
D ob2;
cout << sizeof(ob1) << sizeof(ob2) << "\n";
}
Run Code Online (Sandbox Code Playgroud)
我预计ob2的大小为20,但输出为24
为什么我们不对非虚函数使用相同的方法?
我的意思是,为什么我们以这种方式使用虚函数?我们不能只将它们用作非虚拟的并覆盖它们吗?
如果这种方法节省了我们的时间/空间或者什么,我们为什么不对非虚函数使用相同的方法呢?我的意思是,对于特定的类,将有一个函数表是有意义的.
无论如何,提前谢谢,我只是有点困惑.
受理查德鲍威尔的这次cppcon谈话的启发,我创建了以下代码片段来愚弄:
#include <iostream>
using std::cout;
using std::endl;
struct erdos
{
void who()
{
cout << "erdos" << endl;
}
float f1;
float f2;
};
struct fermat : public erdos
{
float f3;
};
struct fermat2 : public fermat
{
float f4;
};
struct fermat3 : public fermat2
{
float f5;
};
int main(void)
{
erdos e;
cout << "sizeof(e)" << sizeof(e) << endl;
fermat f;
cout << "sizeof(f)" << sizeof(f) << endl;
fermat2 f2;
cout << …Run Code Online (Sandbox Code Playgroud) 在对象切片中,当派生类对象复制到基类对象时,派生类的_vptr是否也会像基类的其他成员一样复制到基类的_vptr?如果没有,为什么?
class Base {
public :
virtual void Display() { cout << "In Base" << endl; }
};
class Derived:public Base {
public:
void Display() { cout << "In Derived" << endl; }
};
int main()
{
Derived objD;
Base objB;
objB = objD;
objB.Display();
}
Run Code Online (Sandbox Code Playgroud)
我观察到上述代码片段的以下结果。
Output
In Base
Run Code Online (Sandbox Code Playgroud) 我读到一个抽象类仍然可以有一个表.但我对它在vtable中会有多少条目感到困惑.例如,如果我的抽象类是:
class Circle(){
virtual void draw() = 0;
}
Run Code Online (Sandbox Code Playgroud)
然后在其vtable中有多少条目?另外,我是否正确地说这个抽象类在其vtable中有1个条目?谢谢你的帮助.
class Circle(){
virtual double a{ return 0.0; }
virtual void draw() = 0;
}
Run Code Online (Sandbox Code Playgroud) 我有一个使用 CRTP 和 C++20 的代码:
template<class Derived>
class Base {
public:
void m() {
static_cast<Derived*>(this)->feature();
}
virtual constexpr void feature() = 0;
}
class BaseImpl: public Base<BaseImpl> {
virtual constexpr void feature() final override { // ... };
}
Run Code Online (Sandbox Code Playgroud)
有没有办法删除vptr,这样对象就不会占用 8 个字节(对于 x64),而只占用 1 个字节?(因为它从不使用运行时多态性)?
在实际代码中,层次结构要复杂得多,有 2 个vptr(因此需要 16 个字节)。是否有类似 GCC 或 MSVC 的扩展?
是的,当然,解决方案之一就是删除该virtual方法。但由于它具有复杂的层次结构,它带来了极其丑陋的错误和不可维护的代码,因为程序员应该通过读取不可读的模板实例化错误来猜测必须实现哪些方法。
整个目标是实现类似 Java 的层次结构(具有接口、覆盖检查等),但具有零成本抽象。
我做了一些实验(探索代码反汇编),编译器完全优化了所有虚拟调用,提供了零成本抽象。除了它将对象扩展为具有vptr我的代码中从未使用过的 a 。
我发现了类似的东西__declspec(novtable),但vptr仍然占用空间。是的,物体的尺寸尽可能小是极其重要的。
例子
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <iomanip>
struct father
{
int variable;
father(){variable=0xEEEEEEEE;};
virtual void sing(){printf("trollolo,%x\n",variable);}
~father(){};
};
struct son:father
{
son(){variable=0xDDDDDDDD;};
virtual void sing(){printf("trillili,%x\n",variable);}
~son(){};
};
int main()
{
father * ifather=new(father);
son * ison=new(son);
father uncle;
father * iteachers;
*((long long*)&uncle)=0xDEAF;
iteachers=(father*)malloc(20*sizeof(father));
//ineffective assignments
iteachers[0]=*ifather;
uncle=*ifather;
ifather->sing();//called to prevent optimization
ison->sing();//only to prevent optimization
std::cout.setf(std::ios::hex);
std::cout<<"father:"<<*((long long*)ifather)<<","<<std::endl;
std::cout<<"teacher0:"<<*((long long*)&(iteachers[0]))<<","<<std::endl;
std::cout<<"uncle:"<<*((long long*)&uncle)<<","<<std::endl;
std::cout<<"(son:"<<*((long long*)ison)<<"),"<<std::endl;
// uncle.sing();//would crash
}
Run Code Online (Sandbox Code Playgroud)
使用 gcc 编译时,teachers[0] 的 vtable 指针为零。叔叔的 vtable 指针也保持其原始值而不是被覆盖。我的问题:为什么会这样?有干净的解决方法吗?我uncle._vptr=ifather->_vptr …
为什么只有默认构造函数才能创建vptr(虚拟表指针)和vtable(虚拟表)?为什么参数构造函数不能