#c #inheritance
#c #наследование
Вопрос:
Предполагая, что у меня есть базовый класс A и общедоступный производный класс B, как я должен назначить объект подобъекту базового класса B?
class A {...};
class B : public A {...};
A a(..);
B b(..);
static_cast<Aamp;>(b) = a; ???
Это выполнимо без написания оператора присваивания для B? Существуют ли какие-либо потенциальные проблемы с приведением b к Aamp;? Соответствует ли это стандарту?
Комментарии:
1. Это хороший вопрос — какой будет семантика при таком назначении?
2. В библиотеке MS VC ATL есть несколько вариантов использования.
3. Очень часто класс только для метода наследует структуру только для данных, например классы доступа к базе данных.
4. 1 Простой простой вопрос, но он имеет очень хорошее напоминание для программистов на C с давним опытом программирования с мелким объектно-ориентированным программированием.
5. Другой способ написать то же
b.A::operator=(a);
самое. Оба не интуитивно понятны, но очень полезны
Ответ №1:
Написание другого ответа, чтобы продемонстрировать, почему и как назначить объект базового класса объекту производного класса.
struct TimeMachineThing_Data {
..
..
};
class TimeMachineThing : private TimeMachineThing_Data
{
static std::stack<TimeMachineThing_Data> m_stateHistory;
void SaveState() {
m_stateHistory.push_back( static_cast<TimeMachineThing_Dataamp;>(*this) );
}
void RestoreState() {
static_cast<TimeMachineThing_Dataamp;>(*this) = m_stateHistory.front();
m_stateHistory.pop_front();
}
};
Это очень полезно и полностью законно.
(Здесь частное наследование, поэтому только внутренне TimeMachineThing — Это TimeMachinetime_Data)
Еще один.
struct StructWithHundresField {
string title;
string author;
...
StructWithHundresField() {
...
}
};
class EasyResetClass : public StructWithHundresField {
int not_reset_this_attriute;
public:
void ResetToInitialStateAtAnyTime() {
static_cast<StructWithHundresFieldamp;>(*this) = StructWithHundresField();
}
}
Комментарии:
1. Однако это редко бывает полезным шаблоном. Композиция должна быть предпочтительнее частного наследования, где это возможно.
Ответ №2:
Это действительно плохая идея. A — базовый, B — производный тип. Преобразуя B в A, вы теперь используете оператор присваивания A, который не будет касаться каких-либо дополнительных производных данных. В конце этого назначения по- b
прежнему считается типом B
, хотя теперь он содержит A
. Это противоположно тому, как предполагается использовать наследование.
Изменение строки на b = reinterpret_cast<Bamp;>(a);
было бы еще хуже. Тогда вы будете притворяться, что a
это a B
, когда это не так, и вы читаете недопустимую память.
Если вы действительно хотите выполнить такое назначение, вы хотите:
class B : public A {
Bamp; operator= (const Aamp; a) { ... }
};
Затем вы можете написать функцию для копирования информации из A
, и как-то справиться с дополнительной информацией в производном типе B
, плюс это позволило бы вам просто написать:
b = a;
Комментарии:
1. Это плохая идея, потому что вы назначаете только часть своих данных. Т.Е. Если
A
имеет членовp, q
иB
добавляет членаr
, когда вы выполняете назначениеstatic_cast<Aamp;>(b) = a;
, вы только назначаетеp,q
и оставляетеr
нетронутым. Скорее всего, это не то, что вы хотите, поскольку это в основном неполное назначение. Поэтому я мог бы сформулировать это так: «Обычно это действительно плохая идея, но если вы понимаете риски, вот как это сделать».2. да, и здесь важны именно эти дополнительные аспекты. Вместо A и B давайте использовать
class Boss : public Employee
. Если вы это сделаетеboss1 = employee1;
, boss1 теперь больше не является боссом. Определенно все еще сотрудник, но не начальник.3. @Tim теперь чувствую, что понимаю ваши рассуждения. Вероятно, вы перепутали назначение с распределением. Нельзя назначить вновь созданный экземпляр ссылке B, но можно изменить часть B в любое время любым способом.
4. Я не путаю назначение с распределением. Я понимаю, что код работает и не топчет память. Я говорю, что это плохая практика, поскольку ее результаты, вероятно, не то, что вы хотели. Как я уже говорил выше, вы можете выполнить назначение, оставив данные производных членов нетронутыми… но действительно ли это то, чего вы хотите? Если вы хотите присвоить производным элементам значение по умолчанию (или интерполированное), используйте оператор присваивания, приведенный выше. Если вы действительно хотите оставить производные элементы как есть, продолжайте — просто убедитесь, что понимаете, что вы делаете, и ПРОКОММЕНТИРУЙТЕ ЭТО ради сопровождающего.
5. @9dan:
A::operator=(a);
Нет ничего плохого в вызове оператора присваивания базового класса из функции-члена производного класса.
Ответ №3:
В C (как и в других языках ООП) наследование устанавливает связь Is-A .
То есть, если B публично наследует A, B = A.
Вы всегда можете привести экземпляр B к ссылке без каких-либо проблем.
Комментарии:
1. Имеет смысл. Я беспокоился только о любых потенциальных проблемах с расположением памяти — я почему-то не могу найти аргумент, почему такие назначения всегда должны работать в C .
2. -1: @ 9dan это FALSE . В памяти B должно отличаться от A. Поэтому при выполнении B = A вы копируете только фрагмент A в B. Результаты недопустимы. Полиморфизм работает за счет использования только указателей и ссылок
3. @Stephane Какое конкретное слово или фраза являются ЛОЖНЫМИ в моем ответе? Если бы я намеревался изменить часть памяти B, неужели я не мог предсказать результат назначения? Вы неправильно поняли равенство типов и равенство данных?
4. @Stephane тогда как вы можете использовать унаследованный метод A с экземпляром B?? Весь метод A должен включать в себя dynamic_cast вещь для будущего наследования?? Или нам нужен static_cast перед передачей экземпляра B функции, ожидающей A ??
5. @Stephane Итак, мы могли бы использовать весь метод A с экземпляром B правильно? И, по мнению A, экземпляры B точно такие же, как и чистый экземпляр A, верно? Это связь IS-A, которую я пытаюсь объяснить.
Ответ №4:
Подумайте на минуту о том, хорошая ли это идея. Помните, что если у вас есть B подкласса A, то каждый B является A, но не каждый A является B. Например, каждая собака является млекопитающим, но не каждое млекопитающее является собакой. Если у вас есть конкретный объект B, попытка присвоить ему объект A в большинстве случаев не является математически четко определенной. Более того, в мире C , поскольку ваш объект B статически типизирован как B, вы никогда не сможете присвоить ему объект типа A таким образом, чтобы он перестал быть B. В лучшем случае вы собираетесь перезаписать только часть объекта B без изменения какой-либо части, специфичной для B.
Ответ №5:
Назначение нарезки безопасно только в том случае, если ваш базовый класс находится в стандартной компоновке: https://en.cppreference.com/w/cpp/types/is_standard_layout . Даже лучше, если ваш производный класс также имеет стандартную компоновку.
В частности, ваш базовый класс не должен содержать виртуальных методов или виртуального деструктора, а все нестатические элементы данных должны иметь одинаковый контроль доступа (например public
, или private
). У вашего базового класса может быть сам базовый класс, и у него могут быть элементы данных, которые являются объектами других классов, но все те классы, которые вы таким образом наследуете в свой базовый класс, также должны иметь стандартный макет.
Если ваш базовый класс имеет стандартную компоновку, то нет ничего плохого в назначении ему нарезки, поскольку это гарантированно касается только элементов данных базового класса. Однако все остальные случаи небезопасны.
Ответ №6:
Я бы сказал, что вам нужен оператор присваивания, который специально копирует объект A в объект B.
В общем, неплохо иметь его любым способом при копировании объектов того же типа. Но объекты разных типов делают это еще более важным.
Ответ №7:
static_cast<TimeMachineThing_Dataamp;>(*this) = m_stateHistory.front();
может быть переписан без приведения как TimeMachineThing_Data amp; data = *this; data = m_stateHistory.front();
.
Ответ №8:
Каждый должен знать, что присваивание является ковариантным двоичным оператором и поэтому не может корректно работать с виртуальными функциями. Это верно для большинства двоичных операторов, но присваивание является особенным, поскольку оно является частью языка C .
Если вы используете OO, ваши объекты должны быть недоступны копированию и всегда представлены указателями. Уникальность идентификатора объекта является сердцем OO: объекты не являются значениями, они имеют уникальное значение (их адрес).
Если вы играете со значениями, вы должны использовать соответствующие концепции: функциональное программирование (FP). Это замыкания (прикладные объекты), переключатели, шаблоны, варианты и другие вещи.
Постарайтесь получить четкое представление о каждом из них, прежде чем смешивать их. В общем случае FP включает OO, такова общая методология: OO — это особый случай, который в особых обстоятельствах обеспечивает безопасную динамическую отправку. Отправка OO является линейной, что означает, что она обрабатывает неограниченный набор подтипов, но она также применяется только к свойствам (функции с одним параметром variant, а именно объект) и не может работать ни для чего более высокого порядка (функции с более чем одним параметром variant). аргумент). Назначение — это просто еще одна 2-арная функция, следовательно, ее нельзя отправлять с помощью виртуальных функций.