我想从普通的老式C结构::sockaddr_storage
派生一个结构,这样我就可以用方法来扩展它来处理自己的数据。我可以像往常一样对派生结构进行类型转换,以访问存储在结构中的不同套接字地址AF_INET6或AF_INET。但我不明白使用虚拟析构函数时的问题,如下所示:
#include <netinet/in.h>
#include <iostream>
namespace myns {
struct Ssockaddr_storage1 : public ::sockaddr_storage {
Ssockaddr_storage1() : ::sockaddr_storage() {}
~Ssockaddr_storage1() {}
};
// This is only different by using a virtual destructor.
struct Ssockaddr_storage2 : public ::sockaddr_storage {
Ssockaddr_storage2() : ::sockaddr_storage() {}
virtual ~Ssockaddr_storage2() {}
};
} // namespace myns
int main() {
{
myns::Ssockaddr_storage1 ss;
std::cout << "Ssockaddr_storage1:\n";
std::cout << "ss_family = " << ss.ss_family << "\n";
sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;
std::cout << "sin6_family = " << sa_in6->sin6_family << "\n";
}
{
myns::Ssockaddr_storage2 ss;
std::cout << "\nSsockaddr_storage2 with virtual destructor:\n";
std::cout << "ss_family = " << ss.ss_family << "\n";
sockaddr_in6* sa_in6 = (sockaddr_in6*)&ss;
std::cout << "sin6_family = " << sa_in6->sin6_family << "\n";
}
}
我编译它:
~$ g++ -std=c++11 -Wall -Wpedantic -Wextra -Werror -Wuninitialized -Wsuggest-override -Wdeprecated example.cpp
输出为:
Ssockaddr_storage1:
ss_family = 0
sin6_family = 0
Ssockaddr_storage2 with virtual destructor:
ss_family = 0
sin6_family = 15752
如图所示,当使用ss
引用继承的sockaddr_storage
时,它总是有效的。但是当使用类型转换指针sa_in6
访问AF_INET6数据时,它只在不使用虚拟析构函数时才有效。如果声明一个虚析构函数,我会得到一个随机的未定义地址族号,它随调用而异。显然,指针没有指向正确的位置。
为什么在声明虚析构函数时,强制转换的指针sa_in6
不指向继承的sockaddr_storage
结构的开始?
1条答案
按热度按时间lh80um4z1#
这是因为这是未定义的行为。
C不是C(显然)。类似的强制转换在C中有效,但在C中无效。在C中,这是未定义的行为,因为C的类型检查更严格。
ss
是Ssockaddr_storage2
,与sockaddr_in6
没有任何关系。这种转换不需要强制转换,因为
sockaddr_storage
只是Ssockaddr_storage2
的超类。现在你有了一个POD C结构体,你可以闭上眼睛,假装它真的是一个
sockaddr_in6
:既然我们已经解决了这个问题,你观察到的行为的原因是,一旦你引入了虚拟继承,大多数C++实现都会添加一个额外的隐藏指针,指向具有虚拟继承的对象,以处理所有的实现细节。这个隐藏指针通常放在类示例的开头,并且这个无效的强制转换现在不再指向
sockaddr_storage
(现在在类的后面开始),而是指向虚表指针。喜乐随之而来。