Покажчик базового класу може вказувати на похідний об'єкт класу. Чому навпаки не відповідає дійсності?


77

Покажчик базового класу може вказувати на похідний об'єкт класу. Чому навпаки неправда без кастингу? Логічно, що базовий клас не матиме достатньо інформації про похідний клас, але похідний клас також повинен мати інформацію про базовий клас. Тут мені не вистачає деяких основ.

Відповіді:


155

Якщо я скажу вам, що у мене є собака, ви можете сміливо припустити, що у мене є домашня тварина.

Якщо я скажу вам, що у мене є домашня тварина, ви не знаєте, чи є ця тварина собакою, це може бути кішка або, можливо, навіть жираф. Не знаючи додаткової інформації, ви не можете впевнено припустити, що у мене є собака.

так само похідний об'єкт є об'єктом базового класу (оскільки це підклас), тому на нього може вказувати покажчик базового класу. Однак об'єкт базового класу не є похідним об'єктом класу, тому його неможливо присвоїти покажчику похідного класу.

(Скрип, який ви зараз почуєте, є аналогією розтягування)

Припустимо, ви зараз хочете купити мені подарунок для мого вихованця.

За першим сценарієм ви знаєте, що це собака, ви можете купити мені повідець, всі задоволені.

За другим сценарієм я не сказав вам, що таке мій улюбленець, тому, якщо ви все одно збираєтеся купити мені подарунок, вам потрібно знати інформацію, яку я вам не розповідав (або просто здогадайтеся), ви купуєте мені повідець виявляється у мене справді була собака, всі задоволені.

Однак якщо у мене насправді була кішка, то ми тепер знаємо, що ви зробили невдале припущення (кидок) і у вас нещасна кішка на повідку (помилка виконання).

ClassCastException Cat


8
+1: поки що найкраща відповідь, IMHO. Проста аналогія, яка інтуїтивно ілюструє проблему.
Олівер Чарльзворт

3
"Для кожної проблеми існує одне рішення, яке є простим, акуратним і неправильним". - Х. Л. Менкен. Інтуїтивні відповіді часто потрапляють до цієї категорії, особливо ОО, описані термінами "реального світу". Іноді вказівник базового класу вказує на об'єкт похідного типу класу, і саме тому (як зазначає питання) ви можете закинути.
Fred Nurk,

1
@Fred: Відповідь не хибна; вона ніколи не діє до точки на базовий клас об'єкт з покажчиком-на-похідним. Як ви вже сказали, іноді припустимо призначати вказівник базового класу на вказівник на похідний.
Олівер Чарлсворт

3
Собака - це тварина, але тварина - це не обов’язково собака, це може бути жираф. Про що говорить ця аналогія? Якщо тварина вважається базовим класом, а собака / жираф - похідними. Оскільки собака / жираф справді є тваринами, тобто дитячий клас повинен мати можливість вказувати на базовий клас? Чи не так має бути?
Zuzu

1
так, якщо ви робите кастинг, ви чітко говорите компілятору замовкнути, тому що у вас є додаткова інформація, якої він не має. Я думаю, що я можу розширити метафору тварини, щоб додати покажчики та трохи вкинути - терпіть зі мною
jk.

12

У нас є два об’єкти.

class A {
   int a;
};

class B : A {
   int b;
};

Виділіть екземпляр B. Ми можемо взаємодіяти з цим як a A*або a B*.

Виділіть екземпляр A. Якби ми відкинули його до B*, чи слід виділити місце для члена b?


11

Е, тому що базовий клас не є похідним класом.

Коли у вас є дійсний вказівник на тип, ви говорите, що об’єкт, на який вказано, матиме певні дані в певних місцях, щоб ми могли їх знайти. Якщо у вас є вказівник на похідний об'єкт, ви гарантуєте, що об'єкт, на який спрямовано, містить усі члени даних похідних даних, але коли ви вказуєте на базу, то це фактично не має цього, і Bad Things Happen ™.

Однак Derived гарантовано має всі члени базових даних у тих самих місцях. Ось чому вказівник на Base насправді може вказувати на Derived.


1
Усі похідні класи не "гарантовано матимуть усіх членів базових даних в однакових місцях".
Fred Nurk,

Щодо відповідного вказівника, я вважаю, що вони є.
Щеня

@Bo: Це лише зробить такі речі, як кастинг та виклики функцій, більш складними. По суті, наслідування на основі покажчика працює виключно на основі того, що коли у мене є вказівник на Base, змінні vtable та member існують там, де вони існували б у звичайному екземплярі Base.
Щеня

6

Оскільки похідний клас включає все, що є в базовому класі. Але базовий клас не включає все, що є у похідному класі.

Не рекомендується вводити тип базового класу до похідного класу. Що станеться, якщо ви спробуєте отримати доступ до членів, які не є частиною базового класу?


Чітке пояснення
Чандра Шекхар,

3

Це справедливо, оскільки тигр - це тварина:

    Animal * pAnimal = new Tiger();

Це не відповідає дійсності, оскільки неправда, що об’єктом є отрута-жаба.

    PoisonDartFrog * pPoisonDartFrog = new GenericFrog();

@DeadMG: Ви застали мене в середині редагування. Я думаю, що приклад допомагає донести концепцію.
Andy Thomas

1
Чи були б сьогоднішні два голоси виборців через три роки прокоментувати те, що вони вважають недосконалим у цій відповіді?
Енді Томас,

... це не обов'язково правда, що об'єктом є отрута-жаба-дротик ...
Арьяман

@Aryaman - Це, звичайно, неправда, за обґрунтованої припущення, що ніхто не визначив GenericFrog як підклас PoisonDartFrog.
Енді Томас,

2
class Base
{
public:
    int a;
}

class Derived : public Base
{
public:
    float b;
}

Base * pBase = new Base();
pBase->a = 7; // setting the value of a in the base

// make a pDerived that points to the SAME DATA as pBase
Derived * pDerived = pBase;
pDerived->a = 5; // this would be okay, base has a public member 'a'
pDerived->b = 0.2f; // error pBase has no data member b and pDerived
                    // points to the SAME DATA as pBase

1

Оскільки C ++ - це статично набрана мова, і дозволяючи неявні перетворення Base-to-Derived може порушити систему типів. Bjarne Stroustrup не хотів помилок виконання "повідомлення не зрозуміло".


На той час, коли Бьярн Страуструп розпочав роботу над C з класами, C навіть не зміг перевірити тип виклику функції :) Але так, C набирається статично, хоча і не дуже сильно.
fredoverflow

1
Ось чому я не розумію виправдання запобігання перетворенню покажчика з точки зору статичного набору тексту. Є багато речей, які можуть піти не так під час виконання («повідомлення не зрозуміле»?), Наприклад, розіменування нульового вказівника, і цілі Страуструпа, схоже, лежать у протилежному напрямку довіри програмісту під час виконання.
Fred Nurk,

@Fred: Можливо, ти ніколи не стикався з мовою, що динамічно набирається, я не знаю. Якби C ++ не був статично набраний, ви могли б просто сказати p->any_method_name(42, "hello", test);, і компілятор не виконував би жодної перевірки під час компіляції, якщо існує такий метод, який приймає три параметри, оскільки він не знав би, що p вказує на екземпляр класу статистично відомого типу. Перевірка буде відкладена на час виконання, що є дорожчим і очевидно менш надійним (але з позитивної сторони, більш гнучким - вам не потрібні підтипи або загальні засоби, наприклад).
fredoverflow

Я знаю Python, серед інших мов.
Фред Нурк

1

Коротка відповідь

class A{
    public: 
        method1();
};

class B: public A{
    public: 
        method2();
};


int main(){

// Case 1
A* ptr_base = new B();
// Here I can call all the methods in A by ptr_base even though it is assigned B ...
// ... because B is derived from A and has all the information about methods of A
// Case 2
B* ptr_derived = new A(); // this will cause error
// Now here ptr_derived is assigned information of A ...
// ... So with this information can I call (*ptr_derived).method2(); ?...
// ... the answer is No because A does not have information of method2() ...;
// ... thus this declaration loses its meaning and hence error.
return 0;
}

0

Оскільки вказівник базового класу може вказувати на екземпляр базового класу або будь-якого похідного типу. Похідний покажчик може вказувати лише на цей похідний тип або будь-який його підклас.

struct Base {};
struct Derived : Base {};
struct Derived2 : Base {};
Base* p = new Derived(); //Fine, Derived inherits from Base
Derived* d = new Base(); //Not fine, Base is not an instance of nor derived from Derived.
Derived* d2 = new Derived2(); // Also not fine, Derived2 derives from Base, but is not related to Derived.

Що стосується причини: Загалом базовий покажчик є більш загальним, ніж похідний покажчик. Як такий він менше знає про успадкований тип. Похідний вказівник не може бути призначений вказівником на базовий тип без кастингу просто тому, що він не може визначити, чи є базовий вказівник похідним типом або одним з його дочірніх елементів.


Приклад був для того, щоб допомогти зрозуміти, чому, і крім того, якби ви потрудились прочитати коментарі, ви помітили б, що там написано ЧОМУ. Додано кілька роз’яснень після пункту, щоб допомогти.
Washu

2
@ AndyThomas-Cramer: Торгувати голосами проти іншого користувача суперечить духу системи, і я вражений тим, що користувач 4k-представника так відверто виступає за подібне зловживання.
Fred Nurk

@Fred Nurk - Я не мав наміру пропонувати quid pro quo. Мій прихильник був безумовним.
Andy Thomas

0

Якщо призначити адресу з покажчика базового класу в покажчик похідного класу, ви можете потенційно призначити об’єкт базового класу похідному покажчику класу. Ви ризикуєте отримати доступ до членів похідного класу, коли у вас немає похідного класу. Тоді як похідні методи класу працюють на базовому класі, вони роблять це лише в тому випадку, якщо метод не отримує доступу до даних членів похідного класу.

Це величезний ризик.

Тож ми змушуємо вас взяти участь у кастингу, щоб вам довелося визнати заяву про відмову від відповідальності (ви можете допустити дурну помилку, будьте обережні).


0

Це тому, що "тип вказівника - це тип об’єкта, на який вказує вказівник". Так,

  1. Якщо ми маємо покажчик базового типу (* B):

тоді ми очікуємо об'єкт базового типу (і хотів би отримати доступ до його функціональних можливостей) за адресою, вказаною B, і якщо ми отримаємо об'єкт похідного типу за цією адресою, тоді ми також зможемо отримати доступ до необхідних функціональних можливостей. Це тому, що похідний тип - це базовий тип.

  1. Якщо ми отримали покажчик типу (* D):

тоді ми очікуємо об'єкт похідного типу за адресою, вказаною D, і якщо ми отримаємо там об'єкт базового типу, то ми не зможемо отримати доступ до інформації про похідний клас з об'єкта базового типу, оскільки базовий тип не є похідним типом.


0

Дії говорять більше, ніж слова. У дитини також може бути об'єкт батьківського класу. якщо ви добре розумієте вказівники, обмеження для вас відсутні. Внизу код надрукував обидва значення покажчиком дочірнього класу (який мав об’єкт батьківського класу). Також довів, що надрукувавши свою адресу. Будь-які пропозиції вітаються!

#include<iostream>
using namespace std;
class Baap{
    public:
        int a;
        void hoo(){ cout<<"hoo\n"; cout<<a;}
};

class Beta:public Baap{
    public:
        int a;
        int b;
        void hee(){ cout<<"hee\n"; }
};

int main(){
    Baap baap;
    baap.a=1;
    Beta *beta=(Beta*)&baap;
    baap.a=3;
    beta->hee();
    beta->hoo();
    cout<<"\n beta = "<<beta<<"\n&baap = "<<&baap;
    return 0;
}
//output
 hee                                                                                                                           
 hoo                                                                                                                           
 3                                                                                                                             
  beta = 0x7ffd11dd3834                                                                                                        
 &baap = 0x7ffd11dd3834 

0

Взагалі, вказівник одного типу не може вказувати на об'єкт іншого типу. Однак є важливий виняток із цього правила, який стосується лише похідних класів.

У цій ситуації вказівник типу BASE*може вказувати на об'єкт типу Derived, тобто вказівник базового класу може вказувати на похідний об'єкт класу, але навпаки не відповідає дійсності, оскільки базовий об'єкт не є об'єктом підкласу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.