Страницы

Поиск по вопросам

Показаны сообщения с ярлыком virtual. Показать все сообщения
Показаны сообщения с ярлыком virtual. Показать все сообщения

среда, 4 марта 2020 г.

UpCast C#.Override и Virtual

#c_sharp #ооп #virtual #override


При создании класса  

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Back
{
    class Bugay
    {
        public virtual void Method()
        {
            string b = "Base: ";
            Console.WriteLine(b  + "Здраствуйте,сударь");
        }
        public void Print()
        {
            Console.WriteLine("Class Bugay");
        }

    }
    class Bugay2 : Bugay
    {
        public override void Method()
        {
            string b = "NotBase: ";
            Console.WriteLine(b + "До свидания,сударь");
        }
        new public void Print()
        {
            //base.Print();
            Console.WriteLine("Class Bugay2");
        }

    }
    class OverVirt
    {
        static void Main(string[] args)
        {
            Bugay2 bugay = new Bugay2();
            bugay.Method();
            bugay.Print();
            Console.WriteLine(new string('-',20));
            Bugay nebugay = bugay;
            nebugay.Method();
            nebugay.Print();
            Console.WriteLine(new string('-', 20));
            Bugay nebugay2 = new Bugay();
            nebugay2.Method();
            nebugay2.Print();
            Console.WriteLine(new string('-',20));
            Bugay2 bugay2 = (Bugay2)nebugay;
            bugay2.Method();
            bugay.Print();
            Console.WriteLine(new string('-', 20));
            Console.ReadKey();
        }
    }
}


при UpCast  , метод который был пере-реализован с помощью override и virtual , вызывается
из класса который наследует базовый класс (Bugay2) , то есть который был пере-реализован
в этом классе Вывод:

NotBase: До свидания,сударь
Class Bugay2
--------------------
NotBase: До свидания,сударь
Class Bugay
--------------------
Base: Здраствуйте,сударь
Class Bugay
--------------------
NotBase: До свидания,сударь
Class Bugay2
--------------------


Почему вызывается метод не из базового класса? 
    


Ответы

Ответ 1



Смотрите. Разница между override-цепочкой и new-цепочкой методов состоит в следующем. При указании virtual/override вызывается метод у вашего реального типа, того, которым на самом деле обладает объект. То, какой при этом тип ссылки, по которой вы совершаете вызов, не важно, лишь бы тип этой ссылки содержал метод из вашей override-цепочки. Поэтому для метода Method() важно то, объект какого класса вы создаёте в реальности, а тип ссылки не важен. При указании new вызывается метод у вашего заказанного типа, то есть, типа ссылки, а не реального типа вашего объекта. Поэтому для метода Print() важно, какой тип вашей ссылки, а тип самого объекта не важен.

среда, 29 января 2020 г.

Использование виртуальных функций

#cpp #virtual #виртуальная_функция


Может ли кто-нибудь пояснить какая от них практическая польза? Дело в том, что я
понимаю механизм их работы, но я не понимаю для чего они нужны и где их можно использовать. 

Рассмотрим на примере:

class Animal
{
public:
    Animal():itsAge(1) { cout << "Animal constructor...\n"; }
    virtual ~Animal() { cout << "Animal destructor...\n"; }
    virtual void Speak() const { cout << "Animal speak!\n"; }
protected:
    int itsAge;
};

class Dog : public Animal
{
public:
    Dog() { cout << "Dog constructor...\n"; }
    virtual ~Dog() { cout << "Dog destructor...\n"; }
    void Speak() const { cout << "Woof!\n"; }
    void WagTail() { cout << "Wagging Tail...\n"; }
}

int main()
{
    Animal *pDog = new Dog;
    pDog->Speak();
    return 0;
}


РЕЗУЛЬТАТ:


Animal constructor...
Dog constructor...
Woof!


Суть использования виртуальных функций в том, что при обращении к методу через указатель
будет вызываться именно тот вариант, который был объявлен как виртуальный в базовом
классе и переопределен в производном. 

Но во-первых, с помощью указателя класса Animal *pDog, всё равно не получишь доступ
к методу WagTail() (махать хвостом), поскольку он не был определен в классе Animal.
 А во-вторых, используя данный механизм придется расплатиться определенными издержками,
связанными с созданием v-таблицы (каждый элемент которой занимает ресурсы оперативной
памяти).

К тому же я не понимаю, зачем передавать указатель на объект производного класса,
когда ожидается указатель на объект базового класса? 

Обе вышеуказанные проблемы можно было бы решить объявив методы и в базовом, и в производном
не виртуальными, а затем написать следующее:

Dog *pDog = new Dog;


вместо:

Animal *pDog = new Dog;


Где "профит"?
    


Ответы

Ответ 1



Рассмотрите классический пример с графическими формами. Вы можете определить класс Shape, который содержит общие методы для всех геометрических фигур, которые вы собираетесь использовать в своем приложении. Этот класс определяет общий интерфейс для всех геометрических фигур. И, допустим, у вас есть форма, на которой вы хотите разместить геометрические фигуры. Форма заранее не знает, какие геометрические фигуры ей придется в себя включать. Она относится к геометрическим фигурам, как к некоторым абстрактным объектам, которые наделены некоторыми методами, которые форма может использовать, чтобы вывести эти фигуры на консоль. Так как к форме можно добавить любое количество геометрических фигур разного вида, то возникает вопрос, а как их в форме хранить? Если нет общего абстрактного представления этих фигур, то их нельзя будет хранить в форме, так как нужен один определенный тип для объектов, чтобы их всех можно было бы хранить в каком-нибудь одном контейнере и не заботиться о том, что фигуры на самом деле различны. Это легко сделать, если наследовать все фигуры от одного класса, как в данном случае от класса Shape, и в этом классе определить виртуальные методы, с которыми форма может работать не зависимо от того, с каким конкретным объектом форма имеет дело. Ниже приведена простая демонстрационная программа, которая реализует описанные идеи. Есть один класс Form , который хранит все геометрические фигуры (в данном случае это объекты классов LeftTriangle, RightTriangle и Rectangle) в стандартном контейнере std::vector, и который имеет метод display, позволяющий вывести все формы на консоль, делегируя каждой фигуре процесс вывода самой себя. // Shape.cpp: определяет точку входа для консольного приложения. // // #include "stdafx.h" #include #include #include #include struct Point { int x; int y; }; class Shape { protected: Point upper_left; char pixel = '*'; public: explicit Shape(Point p = { 0, 0 }) : upper_left(p) { } virtual ~Shape() = default; char set_pixel(char pixel) { char old_pixel = this->pixel; this->pixel = pixel; return old_pixel; } virtual std::ostream & draw(std::ostream &os = std::cout) const = 0; Point move(int dx = 0, int dy = 0) { Point old_upper_left = this->upper_left; this->upper_left.x += dx; this->upper_left.y += dy; if (this->upper_left.x < 0) this->upper_left.x = 0; if (this->upper_left.y < 0) this->upper_left.y = 0; return old_upper_left; } }; class Triangle : public Shape { protected: unsigned int height; public: explicit Triangle(unsigned int height = 1) : height(height) { } }; class LeftTriangle : public Triangle { public: explicit LeftTriangle(unsigned int height = 1) : Triangle(height) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = 0; i < height; i++) { os << std::setw( upper_left.x ) << std::setfill( ' ' ) << "" << std::setw(i + 2) << std::setfill(pixel) << '\n'; } return os; } }; class RightTriangle : public Triangle { public: explicit RightTriangle( unsigned int height = 1) : Triangle( height) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = height; i != 0; i-- ) { os << std::setw(upper_left.x + i - 1 ) << std::setfill( ' ' ) << "" << std::setw( height - i + 2 ) << std::setfill( pixel ) << '\n'; } return os; } }; class Rectangle : public Shape { protected: unsigned int height; unsigned int width; public: explicit Rectangle( unsigned int height = 1, unsigned int width = 1 ) : height(height), width( width ) { } std::ostream & draw(std::ostream &os = std::cout) const override { for (int i = 0; i < upper_left.y; i++) os << '\n'; for (unsigned int i = 0; i < height; i++) { os << std::setw(upper_left.x ) << std::setfill( ' ' ) << "" << std::setw( width + 1 ) << std::setfill(pixel) << '\n'; } return os; } }; class Form { public: Form() = default; void add( Shape * &&shape ) { shapes.push_back(std::unique_ptr( shape )); } std::ostream & display(std::ostream &os = std::cout) const { const int Step = 10; int dx = 0; for (auto &p : shapes) { p->move(dx); p->draw(os) << std::endl; dx += Step; } return os; } private: std::vector> shapes; }; int main() { Form form; form.add(new RightTriangle(5)); form.add(new LeftTriangle(5)); form.add(new Rectangle(5, 5)); form.display(); return 0; } Вывод программы на консоль * ** *** **** ***** * ** *** **** ***** ***** ***** ***** ***** ***** Виртуальные методы определяют общий интерфейс для всех производных классов, позволяя им самим определять реализацию данного интерфейса. Чтобы можно было обращаться к объектам производных классов, как к однотипным объектам, наделенных общими свойствами, их надо привести к какому-то общему типу. Таким общем типом может быть один из общих базовых классов этих объектов. Тем самым достигается полиморфизм, то есть объекты, выглядящие как объекты одного типа, имеют множество форм поведения и представления. Конечно каждый производный класс может дополнительно определять свои члены данных и методы. Но в таком случае это то, что различает их от объектов других производных классов. Например, вы можете сказать, что каждая женщина и каждый мужчина, это человек. Но вы не можете сказать, например, что каждый человек - это женщина, или каждый человек - это мужчина. Если рассматривать женщин и мужчин как людей, то вы можете обращаться к ним независимо от пола, посылая им, как говорят в ООП, различные сообщения. Например, если вы - кондуктор в автобусе, то вы можете потребовать предъявить проездной билет. Для вас женщины и мужчины в автобусе - это пассажиры, и они должны иметь общие свойства такие, как наличие проездного билета. Для этого вы должны рассматривать мужчин и женщин как объектов некоторого общего типа, в данном случае, как пассажиров. Тем не менее мужчины и женщины как объекты своего индивидуального класса различаются. Например, женщины могут рожать, а мужчины не могут (если только мужчин - это не женщина, формально сменившая пол по документам).

Ответ 2



Я не буду расписывать преимущества для всяких собачек или геометрических фигур, я выскажу одно банально звучащее соображение, но которое для моего понимания в свое время многого значило. Когда говорят, что наследование облегчает повторное использование кода, то - о каком коде речь? О том, что производный класс использует код из базового класса? Отнюдь. Такие вещи можно делать простыми вызовами функций. Наследование дает возможность по-новому использовать уже написанный (а то и скомпилированный в виде динамических библиотек) код. Какая-нибудь f(Base*); используется заново без каких-либо изменений, работая с кодом, который и близко не был написан, а может, и даже не проектировался, когда была написана и скомпилирована эта f() - просто это код виртуальной функции в производном от Base класса. Да, это в определенной степени аналог передачи в функции указателей на другие функции, но только в очень определенной степени. А, кроме того, вопрос "зачем нужна передача в функции других функций?", надеюсь, не вызывает у вас недоумения "где же профит?"

Ответ 3



Как известно, С++ является компилируемым статически типизированным языком, что означает разрешимость типов во время компиляции, так вот механизм виртуальных методов расширяет эту возможность для случая, когда программист хочет именно в run time определить тип объекта, применяя, например, dynamic_cast который бы в случае с НЕ полиморным классом не сработал бы, а выдал ошибку компиляции. Где "профит"? допустим есть такой код struct SomeThing { virtual void someGenericOperation() { cout << "base generic operation" } ~SomeThing(); }; struct SomeThingConcrete : SomeThing { void someConcreteOperation() {} void someGenericOperation() { cout << "override generic op" } // override }; SomeThing* p_smth = ...; И пускай у нас такая ситуация в которой мы хотим проверить, что p_smth указывает на тип SomeThingConcrete чтобы была возможность вызывать методы специфические для SomeThingConcrete тоесть - someConcreteOperation(). проверка осуществляется следующим образом SomeThingConcrete* p_concrete = dynamic_cast(p_smth); if (p_concrete != NULL) { p_concrete->someConcreteOperation(); } else { // p_concrete does not point to SomeThingInterface } Тоесть если downcast к типу SomeThing сработал (условие p_concrete != NULL выполнилось), значит, наше предположение потвердилось и p_smth действительно указывает на SomeThingConcrete, в противном случае p_smth указывает на какой - то другой производный от SomeThing класс. Кроме этого, не будь базовый класс полиморфный, получилась бы лапша лишнего кода, например, если передать в функцию указатель на базовый void f(SomeThing*), то из-за свойства статического связывания пришлось бы перегружать функцию f для SomeThingConcrete и для каждого нового наследника от SomeThing - чтобы вызывалась именно переопределённая в производном версия унаследованного метода. Динамическое связывание, применяя встроенный механизм вызова виртуальных методов избавило бы от подобных перегрузок вовсе - тоесть void f(SomeThing* p_smth) // f is a single polymorphic function { p_smth->someGenericOperation(); { int main() { SomeThing* p_smth = new SomeThing(); SomeThing* p_smthConcrete = new SomeThingConcrete(); f(p_smth); // base generic operation f(p_smthConcrete); // override generic operation }

вторник, 21 мая 2019 г.

UpCast C#.Override и Virtual

При создании класса
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;
namespace Back { class Bugay { public virtual void Method() { string b = "Base: "; Console.WriteLine(b + "Здраствуйте,сударь"); } public void Print() { Console.WriteLine("Class Bugay"); }
} class Bugay2 : Bugay { public override void Method() { string b = "NotBase: "; Console.WriteLine(b + "До свидания,сударь"); } new public void Print() { //base.Print(); Console.WriteLine("Class Bugay2"); }
} class OverVirt { static void Main(string[] args) { Bugay2 bugay = new Bugay2(); bugay.Method(); bugay.Print(); Console.WriteLine(new string('-',20)); Bugay nebugay = bugay; nebugay.Method(); nebugay.Print(); Console.WriteLine(new string('-', 20)); Bugay nebugay2 = new Bugay(); nebugay2.Method(); nebugay2.Print(); Console.WriteLine(new string('-',20)); Bugay2 bugay2 = (Bugay2)nebugay; bugay2.Method(); bugay.Print(); Console.WriteLine(new string('-', 20)); Console.ReadKey(); } } }
при UpCast , метод который был пере-реализован с помощью override и virtual , вызывается из класса который наследует базовый класс (Bugay2) , то есть который был пере-реализован в этом классе Вывод:
NotBase: До свидания,сударь Class Bugay2 -------------------- NotBase: До свидания,сударь Class Bugay -------------------- Base: Здраствуйте,сударь Class Bugay -------------------- NotBase: До свидания,сударь Class Bugay2 --------------------
Почему вызывается метод не из базового класса?


Ответ

Смотрите.
Разница между override-цепочкой и new-цепочкой методов состоит в следующем.
При указании virtual/override вызывается метод у вашего реального типа, того, которым на самом деле обладает объект. То, какой при этом тип ссылки, по которой вы совершаете вызов, не важно, лишь бы тип этой ссылки содержал метод из вашей override-цепочки.
Поэтому для метода Method() важно то, объект какого класса вы создаёте в реальности, а тип ссылки не важен.
При указании new вызывается метод у вашего заказанного типа, то есть, типа ссылки, а не реального типа вашего объекта.
Поэтому для метода Print() важно, какой тип вашей ссылки, а тип самого объекта не важен.