2016-06-02 2 views
0

У меня возникли проблемы с использованием виртуальных функций с объявленными объектами с использованием времени автоматического хранения. Вот воспроизводимый сценарий:Виртуальные функции с автоматическим временем хранения не работают

#include <iostream> 

class A { 
    public: 
     A() {} 
     virtual ~A() {} 
     virtual void printClassName() { 
      std::cout << "A" << std::endl; 
     } 
}; 

class B : public A { 
    public: 
     B() : A() {} 
     ~B() {} 
     void printClassName() { 
      std::cout << "B" << std::endl; 
     } 
}; 

class Test { 
    private: 
     A item; 

    public: 
     Test() {} 
     ~Test() {} 
     void setItem(A item) { 
      this->item = item; 
     } 
     A getItem() { 
      return this->item; 
     } 
}; 

int main() { 
    Test t; 
    B item; 

    t.setItem(item); 
    t.getItem().printClassName(); 

    return 0; 
} 

Это печатает «А», в то время как я ожидал бы его для печати B. Мне любопытно, почему.

Спасибо заранее!

+0

Убедитесь, что все базовые классы, не связанные с листом, являются абстрактными. В противном случае работать с классами очень сложно. –

+1

Это может быть полезно прочитать: http://stackoverflow.com/questions/274626/what-is-object-slicing – NPE

+0

@KerrekSB Я попытался сделать 'printClassName()' чисто виртуальным, но это приводит к ошибке компиляции в строке 21 then: 'error: тип поля« A »- абстрактный класс». –

ответ

8

Вы slicing здесь:

void setItem(A item) { 
    this->item = item; 
} 

B часть отрезала, когда вы проходите item в setItem(), поэтому вы теряете основную часть этого. Если A были абстрактными, это было бы более очевидной ошибкой (поскольку вы не можете хранить абстрактный класс по значению).

Вместо этого вы захотите сохранить товар как указатель.

4

Это явление называется объект нарезки.

В основном, когда вы передаете item (типа B) в setItem (который принимает A), вы теряете каждый информация о B, поскольку B не могут быть сохранены в A.

Таким образом, когда вы звоните printClassName, он знает об этой функции только от A, поэтому он называет ее от A.


Одним из возможных решений является передача указателей на объекты вместо необработанных данных. В этом случае вы вызовете printClassName из B, потому что нет нарезки.

3

Просто чтобы было ясно, чтобы избежать объекта нарезку в этом случае вы должны изменить void setItem(A item) к void setItem(A& item) и A getItem() к A& getItem().

Это (прохождение/возвращение по ссылке) сохраняет динамические типы и почти наверняка, что вы собираетесь в этом случае

EDIT: я проглядел тот факт, что объект вы хранение является также типа A , Это не сработает. Вы должны иметь его как указатель, чтобы он мог иметь другой динамический тип из своего статического типа (или ссылки, но тогда его нужно было бы инициализировать в конструкторе).

Таким образом, чтобы перейти к использованию указателя, вы можете попробовать сменить A item в Test на A* item. Затем установите/получите:

void setItem(A& item) { 
     this->item = &item; 
    } 
    A& getItem() { 
     return *(this->item); 
    } 

Это дает желаемый результат. Это нормально, если вы просто проверяете полиморфное поведение, но его нужно считать очень осторожным для фактического дизайна (например, вы можете подумать о том, должно ли быть разделяемое владение с использованием std::shared_ptr).

+0

Я изменил объявление на 'void setItem (A & item)', и «A» все еще печатается. –

+0

Вам также нужно изменить 'getItem()' (см. Править) – Smeeheey

+0

К сожалению, также изменяя 'A getItem()' на 'A & getItem()' все еще печатает "A". –

Смежные вопросы