Эксперт .NET
4430 / 2090 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
1

В C++ метод производного класса всегда переопределяет метод базового класса?

30.07.2019, 00:50. Показов 4661. Ответов 22
Метки нет (Все метки)

C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#pragma once
 
#include <iostream>
 
using namespace std;
 
class Fish
{
public:
    virtual ~Fish() = default;
 
    Fish(bool isFreshWater) : isFreshWaterFish(isFreshWater) { }
 
    virtual void Swim(bool isFreshWater)
    {
        if (isFreshWater)
            cout << "Swims in lake. "  << endl;
        else
            cout << "Swims in sea" << endl;
    }
 
    virtual void Swim()
    {
        if (isFreshWaterFish)
            cout << "Swims in lake" << endl;
        else
            cout << "Swims in sea" << endl;
    }
 
private:
    bool isFreshWaterFish;
};
 
class Tuna : public Fish
{
public:
    // Раскрытие скрытых текущей реализацией методов Swim базового класса
    using Fish::Swim;
 
    Tuna() : Fish(false) 
    {
        cout << "Tuna swims real fast" << endl;
    }
 
    void Swim() override
    {
        cout << "Tuna swims real fast" << endl;
    }
};
 
class Carp : public Fish
{
public:
    Carp() : Fish(true) { }
 
    // В С++ нет такого понятия, как сокрытие метода базового класса, но и одновременно 
    // и не его переопределение. Если в C# можно скрыть метод, но не переопределить, то в 
    // С++ метод автоматом считается переопределяемым.
    void Swim()
    {
        cout << "Carp swims real slow" << endl;
    }
};
В С++ нет такого понятия, как сокрытие метода базового класса, но и одновременно и не его переопределение. Если в C# можно скрыть метод, но не переопределить, то в С++ метод автоматом считается переопределяемым.
Carp::Swim этот метод хоть и не содержит ключевого слова override, всё равно переопределяет метод базового класса. В C#, например, можно объявить метод не написав override и это не будет считаться переопределением, это будет сокрытием. Если я через ссылку типа базового класса вызову метод Swim, то тот самый метод без override будет проигнорирован и вызовется метод базового класса. Я всё верно понял?

C#
Кликните здесь для просмотра всего текста
C#
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
using System;
 
namespace Inheritance
{
    class Program
    {
        static void Main(string[] args)
        {
            Carp carp = new Carp();
            Tuna tuna = new Tuna();
 
            Fish fish = (Fish)carp;
            fish.Swim();
 
            fish = (Fish)tuna;
            fish.Swim();
 
            carp.Swim();
            tuna.Swim();
        }
    }
 
    public abstract class Fish
    {
        public virtual void Swim()
        {
            Console.WriteLine("Fish.Swim");
        }
    }
 
    public class Carp : Fish
    {
        public override void Swim()
        {
            Console.WriteLine("Carp.Swim");
        }
    }
 
    public class Tuna : Fish
    {
        public new void Swim()
        {
            Console.WriteLine("Tuna.Swim");
        }
    }
}
Миниатюры
В C++ метод производного класса всегда переопределяет метод базового класса?  
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
30.07.2019, 00:50
Ответы с готовыми решениями:

Наследование: почему вызывается метод базового класса, а не производного?
Всем привет, такой вопрос, почему вызывается в данном коде метод базового класса, а не производного...

Почему объект производного класса не видит префиксный оператор из базового класса?
Короче создал я базовый класс с перегруженным префиксным оператором ++. Потом чтоб его...

Вызов метода производного класса через обращение к методу базового класса
Добрый день. Изучаю основы ООП, наткнулся на проблему. Если создавать классы внутри main.cpp,...

Как сложить объект базового класса с объектом производного(наследуемого класса)
Как умножить объект базового класса с объектом производного(наследуемого класса): ozenka - объект...

22
Эксперт .NET
4430 / 2090 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
30.07.2019, 01:21  [ТС] 2
Похоже, скрыть можно, если метод базового класса не объявлен как виртуальный.
0
4470 / 2937 / 440
Регистрация: 01.06.2013
Сообщений: 6,192
Записей в блоге: 9
30.07.2019, 01:28 3
Во первых, есть final.
https://docs.microsoft.com/ru-... dvs.120%29
Во вторых
Цитата Сообщение от Casper-SC Посмотреть сообщение
Если я через ссылку типа базового класса вызову метод Swim, то тот самый метод без override будет проигнорирован и вызовется метод базового класса. Я всё верно понял?
Если в базовом классе метод виртуальный, то вызываться будет метод фактически созданного класса.
override нужен только что бы компилятор мог выругаться если в базовом классе точно такого метода нет.
1
Эксперт .NET
4430 / 2090 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
30.07.2019, 01:33  [ТС] 4
Цитата Сообщение от Casper-SC Посмотреть сообщение
Если я через ссылку типа базового класса вызову метод Swim, то тот самый метод без override будет проигнорирован и вызовется метод базового класса.
Это я про C# писал.

Цитата Сообщение от Casper-SC Посмотреть сообщение
Я всё верно понял?
А это про С++ . В общем, редактировал сообщение, получилось непонятно.

Цитата Сообщение от Curry Посмотреть сообщение
Если в базовом классе метод виртуальный, то вызываться будет метод фактически созданного класса.
Понял.
Цитата Сообщение от Casper-SC Посмотреть сообщение
Похоже, скрыть можно, если метод базового класса не объявлен как виртуальный.
Тут я напутал. Если метод базового класса не виртуальный, я создал экземпляр производного класса, а указатель базового класса, то при вызове этого метода вызовется невиртуальный метод базового класса. Только что проверил это. А если метод базового класса виртуальный, то при вызове метода через указатель базового класса вызовется переопределённый метод производного класса. Тоже проверил (собственно, это не противоречит написанному Curry).
1
зомбяк
1564 / 1213 / 345
Регистрация: 14.05.2017
Сообщений: 3,935
30.07.2019, 02:01 5
Casper-SC, для не-vitual методов используется просто определение, либо отсутствие определения метода. Если у класса метод не определен, то используется метод ближайшего по иерархии вверх родителя. Кроме того, для любого экземпляра класса всегда можно вызвать метод требуемого родительского класса (это не зависит от виртуальности):

C++
1
2
Carp carp;
carp.Fish::Swim(); // принудительно выполнить метод родительского класса Fish
В случае virtual-метода будет использован наинизший по иерархии метод, вне зависимости от того, какого именно класса был экземпляр или указатель на экземпляр. Важно, каким классом создавался экземпляр. То есть

C++
1
2
3
Carp carp;
Fish &fish = carp;
fish.Swim();  // вызовется Carp::Swim(), несмотря на то, что ссылка имеет тип Fish
При этом в промежуточных и текущем классах отсутствие ключевого слова virtual ни на что не влияет - метод становится виртуальным, если хотя бы в одном из родительских классов он таковым объявлен (ну и соответственно с этого уровня иерархии и будет действовать данное свойство).

override тоже в общем-то ни на что не влияет, и введён только затем, чтобы при случайном удалении слова virtual у какого-то из родительских классов в дочернем возникала ошибка компилятора, показывающая необходимость именно виртуального способа вызова.
2
4470 / 2937 / 440
Регистрация: 01.06.2013
Сообщений: 6,192
Записей в блоге: 9
30.07.2019, 02:03 6
Цитата Сообщение от TRam_ Посмотреть сообщение
override тоже в общем-то ни на что не влияет, и введён только затем, чтобы при случайном удалении слова virtual
ну, не только. Шире. Что бы при рефакторинге одинаково сигнатуры функций изменить.
2
зомбяк
1564 / 1213 / 345
Регистрация: 14.05.2017
Сообщений: 3,935
30.07.2019, 02:13 7
И ещё, по коду выше, насчёт виртуального деструктора. Он наследующим классам не передаётся, потому если нужно удалять объект на основе указателя на родительский класс, т.е. по принципу
C++
1
2
Fish *p_fish = new Carp;
delete p_fish;
то virtual ~Carp() = default; объявлять обязательно, и всем классам выше по иерархии тоже! В противном случае для корректного удаления обязательно при удалении явно преобразовывать тип в тот, которым он создавался.
Наподобие
C++
1
2
Fish *p_fish = new Carp;
delete static_cast<Carp *>(p_fish);
1
285 / 176 / 21
Регистрация: 16.02.2018
Сообщений: 666
30.07.2019, 03:48 8
Цитата Сообщение от TRam_ Посмотреть сообщение
virtual ~Carp() = default; объявлять обязательно, и всем классам выше по иерархии тоже! В противном случае для корректного удаления обязательно при удалении явно преобразовывать тип в тот, которым он создавался.
Откуда ты это взял?
1
Эксперт .NET
4430 / 2090 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
30.07.2019, 09:51  [ТС] 9
Цитата Сообщение от rat0r Посмотреть сообщение
Откуда ты это взял?
Цитата Сообщение от TRam_ Посмотреть сообщение
И ещё, по коду выше, насчёт виртуального деструктора.
Про это я в курсе. Во-первых ReSharper C++ автоматом раздаёт советы по улучшению кода и объясняет кратко в чём проблема. Решарпер мне эту строку в код и добавил. Потом я уже нагуглил:
http://cpp-reference.ru/articl... estructor/
0
Комп_Оратор)
Эксперт по математике/физике
8758 / 4500 / 605
Регистрация: 04.12.2011
Сообщений: 13,428
Записей в блоге: 16
31.07.2019, 11:18 10
Цитата Сообщение от Casper-SC Посмотреть сообщение
В C++ метод производного класса всегда переопределяет метод базового класса?
Casper-SC, слово "всегда" в С++ означает писать книгу. В С++ всегда (не буду так) обычно, есть способ избежать необходимости для отдельно выбранного производного класса или группы таковых, переопределять метод базового класса (кроме деструкторов).
0
Эксперт .NET
4430 / 2090 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
31.07.2019, 21:24  [ТС] 11
Цитата Сообщение от IGPIGP Посмотреть сообщение
обычно, есть способ избежать необходимости для отдельно выбранного производного класса или группы таковых, переопределять метод базового класса (кроме деструкторов).
Зачем? Иногда по логике их нужно переопределять. Похоже, мы друг друга не поняли (у меня такое впечатление). То что в С++, не нужно специально переопределять методы в производных классах меня не удивляет. Так и должно быть.

Цитата Сообщение от IGPIGP Посмотреть сообщение
слово "всегда" в С++ означает писать книгу
Хотелось бы услышать более человеческим языком. Я не знаком с выражением "писать книгу" и не понимаю, о чём идёт речь.
0
308 / 220 / 74
Регистрация: 23.05.2011
Сообщений: 981
31.07.2019, 21:53 12
Цитата Сообщение от TRam_ Посмотреть сообщение
то virtual ~Carp() = default; объявлять обязательно, и всем классам выше по иерархии тоже! В противном случае для корректного удаления обязательно при удалении явно преобразовывать тип в тот, которым он создавался.
Зачем?
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
 
class Base{
public:
    virtual ~Base(){std::cout<<"Base\n";}
};
 
struct Checker{
    ~Checker(){std::cout<<"Checker\n";}
};
 
class Inherited: public Base{
    Checker bb;
public:
    ~Inherited(){std::cout<<"Inherited\n";}
};
 
int main()
{
    Base* b = new Inherited();
    delete b;
}
Код
Inherited
Checker
Base
0
Эксперт С++
8703 / 4287 / 954
Регистрация: 15.11.2014
Сообщений: 9,728
31.07.2019, 23:21 13
Цитата Сообщение от TRam_ Посмотреть сообщение
то virtual ~Carp() = default; объявлять обязательно, и всем классам выше по иерархии тоже! В противном случае для корректного удаления обязательно при удалении явно преобразовывать тип в тот, которым он создавался.
WTF?
0
зомбяк
1564 / 1213 / 345
Регистрация: 14.05.2017
Сообщений: 3,935
31.07.2019, 23:30 14
hoggy, ты уже третий, указывающий на эту мою ошибку. Да, я действительно всегда считал, что виртуальность деструктора не наследуется. Признаю свою ошибку.
0
Комп_Оратор)
Эксперт по математике/физике
8758 / 4500 / 605
Регистрация: 04.12.2011
Сообщений: 13,428
Записей в блоге: 16
01.08.2019, 00:47 15
Цитата Сообщение от Casper-SC Посмотреть сообщение
Зачем? Иногда по логике их нужно переопределять.
Кто говорит, что не нужно иногда. Я говорю о вопросе:
Цитата Сообщение от Casper-SC Посмотреть сообщение
В C++ метод производного класса всегда переопределяет метод базового класса?
И утверждаю, что
Цитата Сообщение от IGPIGP Посмотреть сообщение
обычно, есть способ избежать необходимости для отдельно выбранного производного класса или группы таковых, переопределять метод базового класса (кроме деструкторов).
То есть, всегда, не удачное слово, для такого языка.
Цитата Сообщение от Casper-SC Посмотреть сообщение
То что в С++, не нужно специально переопределять методы в производных классах меня не удивляет.
Что значит в С++ не нужно? С++ это не лес, который определяет нужность или не нужность в его чаще, что-то определять. Нужно ли переопределять решает программист. При чём тут С++? Casper-SC, если бы вы спросили о том, как переопределяются виртуальные методы или что-то ещё, в менее общих, но тем не менее категоричных формулировках, легче было бы вас понять.
Но если кратко, то мой ответ на вопрос топика:
Цитата Сообщение от Casper-SC Посмотреть сообщение
В C++ метод производного класса всегда переопределяет метод базового класса?
Нет.
Могут быть методы которых вообще нет в базовом классе.
По примеру, - метод совпадающий по сигнатуре с виртуальным, - всегда переопределение, да.
override - подсказка компилятору о том, что вы намереваетесь этим методом перегрузить метод в базовом классе. Это спасёт, например, если вы ошибётесь в сигнатуре и не заметите этого. Беда будет (без override) не только в том, что вы не получите переопределения. Вы потеряете сам оригинал виртуального метода в наследнике.
1
4470 / 2937 / 440
Регистрация: 01.06.2013
Сообщений: 6,192
Записей в блоге: 9
01.08.2019, 01:04 16
Цитата Сообщение от IGPIGP Посмотреть сообщение
Это спасёт, например, если вы ошибётесь в сигнатуре и не заметите этого. Беда будет (без override) не только в том, что вы не получите переопределения. Вы потеряете сам оригинал виртуального метода в наследнике.
Почему он потеряется? Он будет доступен, у него же другая сигнатура.
Хуже, если метод повторить в наследнике не предполагая что у базового уже есть такой виртуальный. Такое возможно, особенно, с короткими именами и простыми сигнатурами.
Интересно, есть ли в каких ни будь компиляторах флаг включающий предупреждение для таких случаев?
0
5535 / 3021 / 1261
Регистрация: 07.02.2019
Сообщений: 7,638
01.08.2019, 01:35 17
Цитата Сообщение от Curry Посмотреть сообщение
Хуже, если метод повторить в наследнике не предполагая что у базового уже есть такой виртуальный.
Я хоть и новичек, но мне трудно представить, почему это может стать проблемой. Если вы переопределили виртуальный метод базового, то вызывая его из потомка вы его и вызовите. А если вы вызываете его по ссылке на базовый класс(не предполагая что он виртуальный), то тут что-то не то(с программистом), но и в этом случае вызовется именно он.
1
Комп_Оратор)
Эксперт по математике/физике
8758 / 4500 / 605
Регистрация: 04.12.2011
Сообщений: 13,428
Записей в блоге: 16
01.08.2019, 09:25 18
Цитата Сообщение от IGPIGP Посмотреть сообщение
вы намереваетесь этим методом перегрузить метод в базовом классе
переопределить! "перегрузить" - тут совершенно неуместно. Интересно читать утром то, что ночью пишешь.

Добавлено через 18 минут
Цитата Сообщение от Curry Посмотреть сообщение
Почему он потеряется? Он будет доступен, у него же другая сигнатура.
Он будет доступен лишь через указатель базового класса. А через ссылку на наследника он исчезнет сразу как только появится "братец" с иной сигнатурой. Для того чтобы он снова появился, придётся написать перегрузку с точно той что нужно сигнатурой явно. Когда нет такого "братца" не нужно ни чего явно писать. И так он у вас есть (не переопределённый правда)
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
 
using namespace std;
struct Base
{
    virtual void foo(){cout<<"\nbase\n";}
    virtual void nobro(){cout<<"\nnobro\n";}
 
};
 
struct ChildDefault : public Base
{
    void foo(int){cout<<"\nchild non-virtual\n";}
 
 
};
 
int main()
{
ChildDefault chd;
Base * bs(&chd);
bs->foo();
//chd.foo();//нету -раскомментируйте и получите ошибку
chd.nobro();//тут нормально
    return 0;
}
2
Эксперт .NET
4430 / 2090 / 404
Регистрация: 27.03.2010
Сообщений: 5,657
Записей в блоге: 1
01.08.2019, 20:07  [ТС] 19
Цитата Сообщение от IGPIGP Посмотреть сообщение
Для того чтобы он снова появился, придётся написать перегрузку с точно той что нужно сигнатурой явно.
Можно написать в производном классе using Base::foo; и ничего не придётся переопределять явно.

Работает:
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
 
using namespace std;
 
struct Base
{
    virtual void foo() { cout << "\nbase\n"; }
    virtual void nobro() { cout << "\nnobro\n"; }
};
 
struct ChildDefault : public Base
{
    using Base::foo;
 
    void foo(int) { cout << "\nchild non-virtual\n"; }
};
 
int main()
{
    ChildDefault chd;
    Base* bs(&chd);
    bs->foo();
    chd.foo();     // Заработало благодаря using Base::foo;
    chd.nobro();
    return 0;
}
1
Комп_Оратор)
Эксперт по математике/физике
8758 / 4500 / 605
Регистрация: 04.12.2011
Сообщений: 13,428
Записей в блоге: 16
01.08.2019, 20:20 20
Цитата Сообщение от Casper-SC Посмотреть сообщение
Можно написать в производном классе using Base::foo; и ничего не придётся переопределять явно.
Можно всё сделать через место, которым не нужно. Есть вообще, возможность определить реализацию по умолчанию, для чисто виртуального метода, например. Только к месту ли? Я же говорил о ключевом слове override. Ведь если кто-то нечаянно изменит сигнатуру или вы сами ошибётесь, то все методы с верной сигнатурой вниз по иерархии будут отрезаны. И чувствовать будут себя отлично. Все будет работать. Не везде как хотелось бы. О перегрузке же был вопрос?
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
01.08.2019, 20:20
Помогаю со студенческими работами здесь

Создание указателя типа базового класса на экземпляр производного класса
Добрый день! Иногда видел коды, где создавался указатель типа базового класса на объект класса -...

Указатель на объект базового класса и адрес объекта производного класса
Пример кода: class Class1 { public: Class1(int x) { j = new int; *j = x; }...

Вызвать конструктор производного класса без конструктора базового класса
Здравствуйте! У меня есть базовый класс треугольник и производный класс равносторонний...

Возможно ли указатель производного класса инициализировать объектом базового класса?
имеется связка наследуемых классов A-&gt;B а от B наследуются одновременно еще два класса B-&gt;C и B-&gt;D...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
20
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2022, CyberForum.ru