在我的程序中,我决定用一个名为Socket的类包装"sockets接口"(实际上只是我正在使用的部分)。我使用两个不同的"域":AF_PACKET和AF_INET。因为这些域有不同的地址结构,我决定也包装它们的地址。
我做了一个小课堂,如下所示:
class SockAddress
{
public:
SockAddress();
virtual sockaddr* getAddress();
virtual socklen_t getAddrLen();
private:
sockaddr address_;
};
基于这个类,我创建了另一个类,它没有返回sockaddr*
,而是返回sockaddr_ll*`
virtual sockaddr_ll* getAddress();
...
sockaddr_ll address_;
在C中,它们都是指针。我只知道,在任何需要sockaddr*
的地方,如果我传递sockaddr_ll*
(并传递相应的socklen_t
大小值),就不会有问题。
但在C++中,当尝试编译时,我会得到一个invalid covariant return type
错误。我已经在这里、这里、这里读到了这个错误,我理解它的含义。无论如何,我找不到"变通"的方法。
我的问题是:既然我想返回指针(指针的大小相同),有没有办法强制编译器接受sockaddr_ll*
作为sockaddr*
?如果有办法的话,我该怎么做?
(如果没有办法,解决这个问题的"正确方法"是什么?)
通常,如果您的公共API公开指针并鼓励类型转换,则需要重新设计。
当你在类中包装一些东西时,重点是隐藏细节,而不仅仅是为它们制作一个类似结构的holder。
您正在设计新的类型,您希望将其作为内置类型。这意味着将所有那些讨厌的指针隐藏在类内部。
例如,创建一个只引用抽象事物的顶级类,如果你想支持不同的底层API,那么就创建子类。。。但将这些API完全封装在抽象操作后面的子类中,如listen/accept/connect。
class Socket
{
public:
Socket(IPAddress address, int port);
virtual ~Socket() = 0; // close/clean up resources
virtual void connect() = 0;
...
};
然后对Socket类进行子类化,封装底层API的细节。
另一种选择是颠倒事物,使SockAddress具有隐藏的操作,并接受要处理的抽象套接字:
class SockAddress
{
void doSomethingForSocket(Socket *s) { ... }
}
我通常会告诉人们先编写使用API的程序,并假设你有一个摇滚风格的API,可以为你做一些很酷的事情:
Socket s("192.168.5.100", 80);
s.connect();
int i;
i << s;
...
ServerSocket server("*", 80);
Socket *client;
while((client = server.accept()) != null) {
...
}
然后着手制作API。在构建API本身时使用相同的技术。在编写connect()时,可能存在的下一组"美好事物"是什么。。。。然后做那些。
在C中,它们都是指针。我只知道在任何地方都需要sockaddr*,如果我通过sockaddr_ll*
我想要一个肯定的参考。我确信它打破了C.的混叠规则
当我使用的编译器上没有协变返回时,我已经实现了这样的协变返回。
class SockAddress
{
public:
SockAddress();
sockaddr* getAddress() { return doGetAddress(); }
protected:
virtual sockaddr* doGetAddress() { return ...; }
};
class SockAddress_ll: public SockAddress
{
public:
SockAddress();
sockaddr_ll* getAddress() { return static_cast<sockaddr_ll*>(doGetAddress()); }
protected:
sockaddr* doGetAddress() { return ...; }
};
通过reinterpre_cast更改static_cast可能会达到您想要的效果。做这件事有多明智是另一回事。
首先,在虚拟函数中,您确实想要相同的返回类型。这是一种给定的情况,因为使用了虚拟函数,所以您可以拥有同一函数的不同版本,并根据对象类型使用正确的版本。因此,无论谁调用您的函数,都不知道将使用哪个版本的函数,因此,如果它们不同,也就不知道返回类型。
因此,无论哪种情况,都返回sockaddr。如果您发现您的调用者需要知道返回的实际类型,那么您可能不想从虚拟函数开始,或者您想要一个非虚拟的"getSpecificAddress"或类似的东西。