С++: рождение родительского класса в дочерний класс
Я новичок в С++, и это проблема, которая у меня есть:
У меня есть два класса: Client
и Host
. И когда все загружено, вы можете нажать две кнопки, если вы нажмете кнопку 1 Client
, и если вы нажмете кнопку 2 Host
, она будет загружена.
Теперь оба Client
и Host
являются довольно большими классами, и я не хочу помещать их в память. Поэтому моя идея заключалась в создании класса Base
, а затем оба Client
и Host
должны расширять базовый класс, и тогда единственное, что я должен был сделать, это следующее:
Base connection;
//If button 1 is pressed:
connection = Client();
//If button 2 is pressed:
connection = Host();
Хорошо, это звучало почти слишком хорошо, чтобы быть правдой, и когда я попробовал, у меня не было ошибок. Теперь возникает проблема, Base
имеет функцию с именем A
, а Client
имеет функцию, называемую B
. Таким образом, функция B
уникальна для класса Client
.
Когда я пытаюсь вызвать функцию B
, я получаю эту ошибку: 'class Base' has no member named 'B'
. Как я могу позволить С++ знать, что я говорю с классом Client
или Host
вместо Base
? Я также открыт для совершенно нового подхода к этой проблеме. Возможно, это просто ошибка в моем процессе мышления.
Спасибо заранее!
Ответы
Ответ 1
Вы столкнулись с ситуацией, которую мы называем срезом объектов, которая является общей проблемой в языках со значениями семантики, такими как С++.
Нарезка объектов происходит, когда вы назначаете значение подтипа новому местоположению (вашей переменной connection
) супер-типа. Это вводит новую копию экземпляра, но супер тип, а не подтип, поэтому вы теряете информацию о конкретном классе, который вы хотели бы создать.
Чтобы избежать этого, у вас есть несколько параметров.
В классическом подходе используются указатели:
Base * connection;
connection = new YourConcreteType();
Затем, чтобы использовать этот объект, вы должны его устранить, используя оператор звездочки (*
):
(*connection).function();
connection->function(); // syntactic sugar, semantically equivalent
Не забыть: вы должны удалить объект после использования:
delete connection;
Чтобы упростить это, С++ представляет два понятия: ссылки и интеллектуальные указатели. В то время как первое имеет ограничение, которое назначается только один раз, оно является синтаксически простым. Последнее похоже на подход указателя, но вам не нужно заботиться об удалении, поэтому вы с меньшей вероятностью столкнетесь с ситуацией утечки памяти:
std::shared_ptr<Base> connection;
connection = make_shared<YourConcreteType>(); // construction via 'make_shared'
// ... use as if it were just a pointer ...
connection->function();
// no delete!
Существуют также другие типы "умного указателя", такие как unique_ptr
, которые можно использовать, если вы не собираетесь передавать указатель (если он остается в области видимости).
Теперь вы можете реализовать функции в обоих классах отдельно. Чтобы использовать полиморфизм, это означает, что во время выполнения вызывается либо функция одного подкласса, либо другого подкласса, в зависимости от того, какой из них был создан, вы должны объявить функции в базовом классе как virtual
, иначе, определение функции в Base
будет вызываться независимо от конкретного типа, который вы создали.
В вашем случае вы хотите вызвать функцию, которая должна делать что-то другое, в зависимости от типа. В то время как ваш подход состоял в том, чтобы ввести две разные функции, а именно A
и B
, вы можете просто объявить одну функцию, называть ее handleEvent
как чистую виртуальную (= абстрактную) функцию в базовом классе, что означает "эта функция должна быть реализована в подклассах" и определить ее в двух подклассах самостоятельно:
Base {
....
virtual void handleEvent(...) = 0; // "= 0" means "pure"
};
// Don't provide an implementation
Client {
void handleEvent(...); // override it
};
// define it for Client:
void Client::handleEvent(...) {
...
}
Host {
void handleEvent(...); // override it
};
// define it for Host:
void Host::handleEvent(...) {
...
}
Ответ 2
Когда вы это сделаете:
Base connection;
Вы создаете объект connection
, который имеет точно память, необходимую для типа Base
.
Когда вы это сделаете:
connection = Client();
вы не расширяете выделенную или преобразованную память connection
в экземпляр Client
. Вместо этого вы создаете более крупный объект, который "нарезается" на меньший.
То, что вы хотите сделать, - это указатели на использование (или ссылки или интеллектуальные указатели), так что то, что вы храните, - это просто адрес объекта, который может быть одним типом или другим типом.
Вот так:
Base *connection;
...
connection = new Client();
Первый оператор создает указатель на Base
типизированный объект, а второй выделяет память для Client
типизированного, инициализирует его и присваивает свой адрес connection
.
Ответ 3
Если вы хотите использовать Base по-прежнему, вам нужно будет сделать это, чтобы использовать эти функции:
if (Button1) {
dynamic_cast<Client*>(connection)->A();
} else {
dynamic_cast<Host*>(connection)->B();
}
И вам нужно будет установить соединение с указателем. Base * connection
.
Это не идеальный вариант. Вы должны исследовать другой способ сделать это, как в других ответах.
Ответ 4
Во-первых, эта строка
connection = Client();
использует оператор присваивания для установки состояния connection
, a Base
из временного объекта Client
. connection
все еще является объектом Base
. Вы можете сделать следующее:
std::unique_ptr<Base> makeBase(someFlagType flag)
{
if (flag) {
return std::unique_ptr<Base>(new Cient);
} else {
return std::unique_ptr<Base>(new Host);
}
}
Тогда
std::unique_ptr<Base> b = makeBase(myFlag);
b->someBaseMethod();
Что касается части каста, я бы сказал, что если вам приходится бросать дочерний тип, вы должны подумать о дизайне классов.