В моей программе я решил обернуть «интерфейс сокетов» (фактически только те части, которые я использую) классом, который я назвал Socket. Я использую два разных «домена»: 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_;
В Си все они указатели. Я просто знаю, что везде sockaddr*
требуется, если я прохожу sockaddr_ll*
(и передать соответствующий socklen_t
значение размера) не будет никаких проблем.
Но в C ++ при попытке компиляции я получаю invalid covariant return type
ошибка. Я уже читал об этой ошибке Вот, Вот, Вот, и я понимаю, что это значит. Во всяком случае, я не мог найти способ обойти это.
Мой вопрос: так как я хочу вернуть указатели (и указатели будут иметь одинаковый размер), есть ли способ заставить компилятор принять sockaddr_ll*
как sockaddr*
? И, если есть способ, как мне это сделать?
(Если нет способа, каким может быть «правильный путь» для решения этой проблемы?)
В общем, если ваш публичный API предоставляет указатели и поощряет приведение типов, вам нужно переделать дизайн.
Когда вы оборачиваете что-то в классе, дело в том, чтобы скрывать детали, а не просто сделать их структурным держателем.
Вы проектируете новый типы, которые вы хотите действовать, как если бы они были встроенными. Это означает, что нужно скрывать все эти неприятные указатели внутри класса.
Например, создайте класс верхнего уровня, который ссылается только на абстрактные вещи, затем создайте подкласс, если вы хотите поддерживать различные низкоуровневые 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 (), какой следующий нижний набор «хороших вещей» может существовать, которые было бы очень приятно иметь… тогда сделайте их.
В Си все они указатели. Я просто знаю, что везде требуется 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 ...; }
};
изменение static_cast с помощью reinterpret_cast, вероятно, сделает то, что вы хотите. Насколько мудро это делать — другое дело.
Прежде всего, в виртуальной функции вам действительно нужен тот же тип возврата. Это своего рода данность, поскольку используются виртуальные функции, поэтому у вас могут быть разные версии одной и той же функции, причем правильная используется в зависимости от типа объекта. Таким образом, тот, кто вызывает вашу функцию, не знает, какая версия функции будет использоваться, и, следовательно, не может знать тип возвращаемого значения, если они отличаются.
таким образом, вернуть sockaddr в любом случае. Если вы обнаружите, что ваш вызывающий должен знать фактический возвращаемый тип, вы, вероятно, не хотите использовать виртуальные функции для начала, или вам нужен не виртуальный «getSpecificAddress» или что-то в этом роде.