为什么虚拟功能在分配"new"时不能取消实现?
Why virtual function can't be unimplemented when allocated with 'new'?
struct A
{
virtual void foo(); // unused and unimplemented
virtual void bar () {}
};
int main ()
{
A obj; // ok
obj.bar(); // <-- added this edition
A* pm = (A*)malloc(sizeof(A)); // ok
A* pn = new A; // linker error
}
对于堆栈上的对象,它工作正常。但是对于在堆上分配new
(不是malloc
(,它会给出链接器错误:
undefined reference to `vtable for A'
因为 malloc 不调用(或在这种情况下尝试调用(A 的构造函数,而 new 会调用。
此代码编译并记录 GCC 发生链接器错误的位置:
#include <cstdlib>
struct A
{
virtual void foo(); // unused and unimplemented
virtual void bar () {}
};
int main ()
{
A obj; // linker error
A* pm = (A*) malloc(sizeof(A)); // ok
A* pn = new A; // linker error
}
首先,此代码不可编译,因为在C++ void *
中不能隐式转换为 A *
。需要显式强制转换。
其次,malloc
的例子完全无关紧要。 malloc
分配原始内存,与任何特定类型完全无关。在这种情况下,malloc
知道注意任何A
,并且它不会创建类型为 A
的对象。
出于这个原因,这个问题的真实例子应该如下所示
struct A
{
virtual void foo(); // unused and unimplemented
virtual void bar () {}
};
int main ()
{
A obj; // ok
A* pn = new A; // linker error
}
问题是为什么第一个声明没有产生更相似的错误,而第二个声明会产生。
从形式上的角度来看,你的程序是无效的,因为它违反了C++语言(特别是ODR(的形式要求。实际上,这两个声明可能或应该产生相同的错误,因为在这两种情况下,对象正式都需要指向 VMT 的指针。在这种情况下,无法创建 VMT,因为某些函数未定义。但是,第一个声明只是因为编译器能够优化第一个声明(而不是第二个声明(对 VMT 的所有引用。编译器也很有可能能够优化整个obj
对象,因为它没有在其他任何地方被引用。
在 GCC 中(因为您似乎正在使用 GCC(,也很容易为第一个声明触发相同的错误
struct A
{
virtual void foo(); // unused and unimplemented
virtual void bar () {}
};
int main ()
{
A obj; // linker error
A *p = &obj;
p->bar();
}
上面的代码将在 GCC 中产生相同的链接器错误,即使此代码中仍未使用未定义的函数foo
。
换句话说,只需添加足够数量的代码即可使编译器认为需要对象的 VMT。在这种情况下,声明之间的行为差异与C++语言无关。这只是特定于编译器的实现问题。
你不能让虚拟函数未实现,即使它是"未使用的"(因为它实际上被 vtable 使用(。这是代码中的错误。
由于编译器中 vtables 的特殊实现,该错误以这种特定方式表现出来。您尚未实现第一个虚拟函数。每当编译器看到类的第一个虚函数的实现时,它都会插入 vtable。既然没有,就没有vtable。
如果未实现第二个函数,链接器将抱怨该特定函数,而不是 vtable。
[编辑]您的编译器可能优化了堆栈上A
的副本,这就是链接器没有抱怨的原因。
malloc
行实际上并不引用类型 A 的对象,这就是它不会产生链接器问题的原因。不过,这条线还有另一个问题:它不应该编译。 malloc
返回不转换为没有强制转换的其他类型的指针的void*
。
A::foo
如果A
在程序中的任何位置实例化。 无论是否实例化是通过局部变量的声明或通过一个新的表达式。 但是,如果此规则是破碎;如果您不提供声明,或者如果您提供两个或更多声明,这只是未定义的行为。 编译器所做的任何事情都是"正确"。 在这种情况下,它可能发生的事情是:
- 需要定义的原因是因为它在 vtable 中被引用,
-
A
的构造函数是内联的,因此初始化 VPTR(并触发 vtable 的实例化(的代码对编译器完全可见, -
由于对象的所有用途对编译器都是可见的,因此它可以看到 VPTR 从未被使用过,因此它只是抑制它。
-
并且没有 VPTR,不需要生成 vtable,因此没有对虚函数的引用。
总之,这取决于编译器的优化方式;你可能会得到一个错误。对于本地声明和新表达式,或者两者都不是,或者对于一个而不是另一个。 这可能取决于优化选项,或者其他什么。 就C++而言,它可能取决于月相,而不是错误,你可能会得到运行它时崩溃的代码(但我首先陈述的场景是最有可能(。
未使用是无关紧要的。定义所有虚函数。就这么简单。
您的自动存储持续时间对象(您选择将对象称为"在堆栈上"(不会 [多态] 使用,因此您不会获得诊断。这不对。
- C++我的数学有什么问题,为什么我的代码不能正确循环
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 为什么我的for循环不能正确获取argv
- 不能在初始值设定项列表中将非常量表达式从类型 'int' 缩小到'unsigned long long'
- 为什么我不能在 FOR LOOP 中使用 i/10,C++?
- 为什么我不能在不创建字符串变量的情况下使用函数的字符串输出
- 为什么模板类中的对象不能返回值
- 为什么我不能在一个类的不同行中声明和定义成员变量?
- 为什么我不能在 C++ 中的特定函数重载中调用同一函数的任何其他重载?
- ld:bind_at_load和-bitcode_bundle(Xcode设置ENABLE_bitcode=YES)不能
- 数组长度,为什么从命令行获取时不能使用它?
- 取消引用运算符不能重载
- 矢量迭代器不能与 std::shared_ptr<> 取消引用
- std::remove() 按预期处理文字,但不能与取消引用的迭代器一起工作
- C++不能取消引用结束列表迭代器
- 为什么我不能取消引用并在C++中引用此字符串
- C ++ map/set 迭代器不能使用 .find 取消引用
- 为什么我不能取消引用迭代器?
- 提振.Asio async_handshake不能取消
- 为什么虚拟功能在分配"new"时不能取消实现?