Форум программистов, компьютерный форум CyberForum.ru
Наши страницы

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
4elovek37
2 / 2 / 0
Регистрация: 10.10.2014
Сообщений: 29
Завершенные тесты: 1
#1

Как правильно использовать operator= при наследовании в полиморфных и неполиморфных классах - C++

31.08.2016, 21:07. Просмотров 278. Ответов 7

Доброго времени суток! Изучаю книгу С. Прата "Язык программирования C++. Лекции и упражнения." Закончил 13-ю главу, посвященную наследованию и приступил к выполнению упражнений. Проблема возникла на этом упражнении:
Кликните здесь для просмотра всего текста

Союз программистов-меценатов собирает коллекцию бутылочного портвейна.
Для ее описания администратор союза разработал класс Port, Завершив определения методов для класса Port, администратор написал
производный класс Vintage Port, прежде чем был уволен

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
class Port//портвейн!
{
private:
    char * brand;
    char style[20];
    int bottles;
public:
    Port(const char * brand = "none", const char * style = "none", 
        int bottles = 0);
    Port(const Port & p);//конструктор копирования
    virtual ~Port() { delete [] brand_; }
    Port & operator=(const Port & p);
    Port & operator+=(int bottles);
    Port & operator-=(int bottles);
    int BottleCount() const { return bottles_; }
    virtual void Show() const;
    friend std::ostream & operator<<(std::ostream & os, const Port & p);
};
 
class VintagePort : public Port
{
private:
    char * nickname;
    int year;
public:
    VintagePort();
    VintagePort(const char *  br, int b, const char * nn, int y);
    VintagePort(const VintagePort & vp);
    ~VintagePort() { delete [] nickname; }
    VintagePort & operator=(const VintagePort & vp);
    void Show() const;
    friend std::ostream & operator<<(std::ostream & os, const VintagePort & p);
};
Вам поручено завершить разработку класса VintagePort.
а. Первое задание — нужно заново создать определения методов Port, т.к.
предыдущий администратор уничтожил свой код.
б. Второе задание — объясните, почему одни методы переопределены, а другие
нет.
в. Третье задание — объясните, почему функции operator= () и operator« ()
не определены как виртуальные.
г. Четвертое задание — обеспечьте определения для методов VintagePort.

В общем и целом все понятно, но увидев пункт "в" я встал в ступор. Функция operator<<() не может быть виртуальной, т.к. она friend. Но почему operator=() не виртуальная? Внимательно проглядел код последних примеров из книги - operator=() не виртуальная, но я почему-то в свое время это забыл и при выполнении упражнений с полиморфным наследованием делал ее виртуальной, и все работало. Покопавшись еще, я совсем запутался, к примеру сделав ф-ю operator= в предыдущем упражнении не виртуальной я получил рабочую программу, хотя вроде и не должно бы:
Кликните здесь для просмотра всего текста

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
64
/empty.h
#ifndef EMPTY_H_
#define EMPTY_H_
#include <iostream>
#include <string>
//Абстрактный базовый класс
class BaseDMA
{
private:
    char * label_;
    int rating_;
public:
    BaseDMA(const char * label = "null", int rating = 0);
    BaseDMA(const BaseDMA & rs);
    virtual ~BaseDMA();
    BaseDMA & operator=(const BaseDMA & rs);
    virtual void View() const = 0;
    friend std::ostream & operator<<(std::ostream &, const BaseDMA &);
    friend std::ostream & operator<<(std::ostream &, const BaseDMA *);
};
//Класс, просто класс
class DMA :public BaseDMA
{
 
public:
    DMA(const char * label = "null", int rating = 0);
    DMA(const DMA & rs);
    ~DMA() {}
    DMA & operator=(const DMA & rs);
    void View() const;
};
 
//Производный класс без динамического выделения памяти
//Деструктор не нужен
//Используется неявный к-р копирования
//Используется неявная операция присваивания
class LacksDMA :public BaseDMA
{
private:
    enum { COL_LEN_ = 40};
    char color_[COL_LEN_];
public:
    LacksDMA(const char * color = "blank", const char * label = "null",
        int rating = 0);
    LacksDMA(const char * color, const BaseDMA & rs);
    void View() const;
};
 
//Производный класс с динамическим выделением памяти
class HasDMA :public BaseDMA
{
private:
    char * style_;
public :
    HasDMA(const char * style = "none", const char * label = "null",
        int rating = 0);
    HasDMA(const char * style, const BaseDMA & rs);
    HasDMA(const HasDMA & hs);
    ~HasDMA();
    HasDMA & operator=(const HasDMA & rs);
    void View() const;
};
 
#endif
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
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
#include <iostream>
#include <cstring>
#include "empty.h"
//Методы BaseDMA
BaseDMA::BaseDMA(const char * label, int rating)
{
    label_ = new char[std::strlen(label) + 1];
    std::strcpy(label_, label);
    rating_ = rating;
}
BaseDMA::BaseDMA(const BaseDMA & rs)
{
    label_ = new char[std::strlen(rs.label_) + 1];
    std::strcpy(label_, rs.label_);
    rating_ = rs.rating_;
}
BaseDMA::~BaseDMA()
{
    delete [] label_;
}
BaseDMA & BaseDMA::operator=(const BaseDMA & rs)
{
    if(this == &rs)
        return *this;
    delete [] label_;
    label_ = new char[std::strlen(rs.label_) + 1];
    std::strcpy(label_, rs.label_);
    rating_ = rs.rating_ + 300;
    return *this;
}
void BaseDMA::View() const
{
    std::cout << "Label: " << label_ << ", Rating: " << rating_ << std::endl;
}
//Методы DMA
DMA::DMA(const char * label, int rating) :BaseDMA(label, rating)
{
    
}
DMA::DMA(const DMA & rs) :BaseDMA(rs)
{
    
}
DMA & DMA::operator=(const DMA & rs) 
{
    if (this == &rs)
        return *this;
    BaseDMA::operator=(rs);
    return *this;
}
void DMA::View() const
{
    BaseDMA::View();
}
 
//Методы LacksDMA
LacksDMA::LacksDMA(const char * color, const char * label, int rating)
    :BaseDMA(label, rating)
{
    std::strncpy(color_, color, 39);
    color_[39] = '\0';//IMHO, TAK SEBE RESHENIE
}
LacksDMA::LacksDMA(const char * color, const BaseDMA & rs)
    
{
    std::strncpy(color_, color, COL_LEN_ - 1);
    color_[COL_LEN_ - 1] = '\0';//IMHO, TAK SEBE RESHENIE
}
void LacksDMA::View() const
{
    BaseDMA::View();
    std::cout << "Color: " << color_ << std::endl;
}
 
//Методы HasDMA
HasDMA::HasDMA(const char * style, const char * label, int rating)
    :BaseDMA(label, rating)
    
{
    style_ = new char [std::strlen(style) + 1];
    std::strcpy(style_, style);
}
HasDMA::HasDMA(const char * style, const BaseDMA & rs)
    :BaseDMA(rs)
 
{
    style_ = new char[std::strlen(style) + 1];
    std::strcpy(style_, style);
}
HasDMA::HasDMA(const HasDMA & hs)
    :BaseDMA(hs)//: BaseDMA(hs)//Вызывает к-р копирования базового класса
{
    style_ = new char[std::strlen(hs.style_) + 1];
    std::strcpy(style_, hs.style_);
}
HasDMA::~HasDMA()
{
    delete [] style_;
}
HasDMA & HasDMA::operator=(const HasDMA & hs)
{
    if(this == &  hs)
        return *this;
    BaseDMA::operator=(hs);//BaseDMA::operator=(hs);//копирование базовой части
    delete [] style_;//Подготовка к операци new для style_
    style_ = new char[std::strlen(hs.style_) + 1];
    std::strcpy(style_, hs.style_);
    return *this;
}
void HasDMA::View() const
{
    BaseDMA::View();
    std::cout << "Style: " << style_ << std::endl;
}
 
//Перегрузка операции << для вызова нужного virtual метода View
std::ostream & operator<<(std::ostream & os, const BaseDMA * hs)
{
    hs->View();
    return os;
}
std::ostream & operator<<(std::ostream & os, const BaseDMA & hs)
{
    hs.View();
    return os;
}
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
#include <iostream>
#include "empty.h"
 
int main()
{
    setlocale(0,"Rus");
    using std::cin;
    using std::cout;
    using std::endl;
    BaseDMA * pdma = new DMA("DMA", 0);
    BaseDMA * phas = new HasDMA("STYLE", "HAS", 0);
    BaseDMA * placks = new LacksDMA("COLOR", "LACKS", 0);
 
    pdma->View();
    placks->View();
    phas->View();
    cout << "PDMA = PLACKS:\n";
    pdma = placks;
    pdma->View();
    placks->View();
    cout << "PLACKS = PHAS:\n";
    cout << (void *)placks << "|||||" << (void *)phas << endl;
    placks = phas;
    cout << (void *)placks << "|||||" << (void *)phas << endl;
    placks->View();
    phas->View();
    system ("pause");
    return 0;
}

Как это работает? Если мы оперируем ссылками на базовый класс, а operator=() не виртуальная (т.е. не используем таблицу виртуальных методов, и компилятор смотрит на тип указателя), то при использовании операции присваивания должна использоваться БАЗОВЫЙ_КЛАСС::operator=(), и соответственно, копироваться только базовая часть! Но в моем коде все прекрасно копируется, кроме того, получается присвоить объекту одного производного класса объект другого производного класса!
У меня ощущение, что я блуждаю в 3-х соснах. Объясните, пожалуйста, как это работает, и как правильно использовать operator=() при полиморфном наследовании и без него. Заранее благодарю.
0
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
31.08.2016, 21:07
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Как правильно использовать operator= при наследовании в полиморфных и неполиморфных классах (C++):

как объявить правильно tr и как его использовать при вызове функции? - C++
main.cpp case 8: cout &lt;&lt; &quot;Firs trapec # is: &quot;; cin &gt;&gt; k; k = k - 1; cout &lt;&lt; &quot;Second trapec # is: &quot;; cin &gt;&gt; j;...

Как использовать один и тот же объект в разных классах? - C++
Подскажите, пожалуйста, как получить доступ к функции через объект. Второй день бьюсь, хотя решение должно быть простое В классе A...

Как правильно использовать заголовочные файлы при раздельной компиляции в MS Visual Studio? - C++
Если я использую функцию cout и cin в каждом модуле, мне нужно в каждом модуле прописывать : #include &lt;iostream&gt; using namespace std; ...

Как работает деструктор и конструктор при наследовании? - C++
Возникла проблемка, не пойму, как работает деструктор и конструктор в моей программе. Я не вызываю явно конструктор, как в этом случае...

Как дополнить вложенный класс при наследовании внешнего? - C++
Допустим, есть класс B, лежащий в классе A. Каким образом класс D, лежащий в C (C наследует A), может наследовать класс B и изменить его?

Как вернуть функцию, которая потерялась при наследовании классов? - C++
Предположим есть два класса: class Parent { public: void foo() { std::cout &lt;&lt; &quot;parent foo &quot; &lt;&lt; std::endl; } }; class Child...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
zss
Модератор
Эксперт С++
6382 / 5947 / 1927
Регистрация: 18.12.2011
Сообщений: 15,264
Завершенные тесты: 1
31.08.2016, 21:15 #2
Цитата Сообщение от 4elovek37 Посмотреть сообщение
pdma = placks;
Это не вызов operator=, а просто присваивание указателю другого адреса.
0
4elovek37
2 / 2 / 0
Регистрация: 10.10.2014
Сообщений: 29
Завершенные тесты: 1
31.08.2016, 21:45  [ТС] #3
zss, пардон, под вечер совсем окосел. Самое обидное, что ведь даже добавил выведение адресов, а все равно проглядел!
C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
pdma->View();
    placks->View();
    phas->View();
    cout << "\nPDMA = PLACKS:\n";
    *pdma = *placks;
    pdma->View();
    placks->View();
    cout << "\nPLACKS = PHAS:\n";
    cout << (void *)placks << "|||||" << (void *)phas << endl;
    *placks = *phas;
    cout << (void *)placks << "|||||" << (void *)phas << endl;
    placks->View();
    phas->View();
    cout << "\nPLACKS = PDMA:\n";
    delete pdma;
    pdma = new DMA("DMA", 0);
    *placks = *pdma;
    placks->View();
    pdma->View();
Вот так стало работать "как надо" (точнее, как "не надо"). И еще раз - я правильно понял, что operator= должен быть строго виртуальным, если мы собираемся работать с полиморфным на следованием? Тогда как правильно ответить на вопрос из задачи С. Прата?
0
Usagi
0 / 0 / 2
Регистрация: 03.07.2016
Сообщений: 18
Завершенные тесты: 2
02.09.2016, 11:24 #4
Операции присваивания не наследуются (стр 716 "Соображения по поводу операции присваивания"). operator<< в данном примере дружественная функция, она не может быть виртуальной - не член класса (там же 719 Прата).

Тоже думал об этом, додумался до написанного мной выше. И тоже читаю этот же учебник -
0
4elovek37
2 / 2 / 0
Регистрация: 10.10.2014
Сообщений: 29
Завершенные тесты: 1
02.09.2016, 13:31  [ТС] #5
Usagi,
operator<< в данном примере дружественная функция
Да, с этим все просто.
По поводу operator=() по ходу решения задачи с портвейном(доставляют в этом учебнике подобные задачи) пришло соображение, почему так делают: Оперируя массивом указателей на базовый класс, мы не можем заранее знать на объект какого класса (базового, или одного из производных) указывает указатель. Теоретически пользователь может попытаться:
1)Присвоить объект производного класса базовому - тут все более менее нормально и при virtual op=() и при non-virtual вызовется оператор присваивания базового класса и мы заберем однозначно существующие данные-члены от объекта произв. класса;
2)Присвоить объект базового класса производному - а вот тут могут возникнуть проблемы: если у нас virtual op=(), то вызовется оператор присваивания производного класса, который, скопировав базовую часть, попытается обратится к данным-членам, которых у присваиваемого объекта базового класса попросту нет. Результат не определен. Если же у нас non-virtual op=(), то мы скопируем только базовую часть, не пытаясь обратиться к несуществующим данным. Смысла в подобных действиях мало (получится смешанный набор данных), но зато ошибок точно не будет.
3)Присвоить объект одного произв. класса объекту другого произв. класса. Тут можно см. п.2, с небольшими отличиями.
Короче говоря, non-virtual op=() гарантирует что при использовании этой операции мы гарантированно скопируем только однозначно существующие данные, принадлежащие базовому классу. За всем остальным к ручному приведению типов, тогда уже пользователь берет ответственность целиком и полностью на себя.
Как то так. Может, я опять что-то напутал, у меня в последнее время такое ощущение все чаще
И тоже читаю этот же учебник
И как Вам? Есть прогресс?
0
Usagi
0 / 0 / 2
Регистрация: 03.07.2016
Сообщений: 18
Завершенные тесты: 2
02.09.2016, 15:03 #6
2)Присвоить объект базового класса производному
Объект базового класса просто вызовет свой operator=(), скопирует базовую часть и на этом успокоится. Члены произвольного класса использоваться не будут. Присваивается же объекту базового класса, поэтому это будет транслировано в базовый_класс.operator=(производный_класс).

3)Присвоить объект одного произв. класса объекту другого произв. класса. Тут можно см. п.2, с небольшими отличиями.
Если все эти оба объекта имеют одного общего предка, то проблем тоже возникнуть не должно. Скопируется общая часть классов - базовая, так как сначала будет вызвана операция присваивания базового класса.

Если operator=() объявлен как виртуальный, всё равно будет вызвана операция базового класса, а потом производного. Модель открытого наследования, отношение "является". Единственное, что мне не понятно, почему его можно объявить виртуальным.

А по учебнику - очень хорошая вещь. Даже купил, хотя и читаю в электроном виде. Прогресс? Конечно. Большая разница между "до" и "после".

Добавлено через 11 минут
Единственное, что мне не понятно, почему его можно объявить виртуальным.
Не так: "Зачем его объявлять виртуальным?".

Добавлено через 13 минут
2)Присвоить объект базового класса производному
Объект базового класса просто вызовет свой operator=(), скопирует базовую часть и на этом успокоится. Члены произвольного класса использоваться не будут. Присваивается же объекту базового класса, поэтому это будет транслировано в базовый_класс.operator=(производный_класс).
Ошибка, утверждение верно для присваивания объекта производного класса объекту базового.
Если присваивать объект базового класса производному, то без явно определённого operator=() для присваивания базового класса производному или конструктора преобразования не сработает.
0
4elovek37
2 / 2 / 0
Регистрация: 10.10.2014
Сообщений: 29
Завершенные тесты: 1
02.09.2016, 15:16  [ТС] #7
Если присваивать объект базового класса производному, то без явно определённого operator=() для присваивания базового класса производному или конструктора преобразования не сработает.
Сработает, если имеем дело с указателями. Все приведенные мной примеры, естественно, при условии использовании указателей на базовый класс (к примеру массив указателей на базовый класс, фактически содержащий смешанный контингент из объектов базового класса и производных). Именно тут и появляются "скользкие моменты", расписанные мной ранее.
Не так: "Зачем его объявлять виртуальным?".
Если используем динамические выделение памяти, и при присваивании нужно выполнять "глубокое копирование".
0
Usagi
0 / 0 / 2
Регистрация: 03.07.2016
Сообщений: 18
Завершенные тесты: 2
02.09.2016, 16:40 #8
Да, через ссылки или указатели такое присваивание будет работать, но только для базовой (общей) части. Неявное восходящие преобразование же?

Я понял, что Вы имеете ввиду: что если работать через ссылки (указатели) и определён виртуальный оператор присваивания, то согласно таблице виртуальных функций при присваивании объекту производного класса объекта базового класса должен быть вызван оператор присваивания производного класса. В этом случае будет вызван только оператор присваивания базового класса. Даже где-то у Праты написано, что операция присваивания относится к специальным членам класса.

У каждого класса свой оператор присваивания. Его нет нужды объявлять виртуальным. Я это имел ввиду.
Для "глубокого копирования" просто берётся и явно определяется оператор присваивания в нужном классе. Это 703 страница Праты. Случай с динамическим выделением памяти (под "глубокое копирование") и без него.
0
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
02.09.2016, 16:40
Привет! Вот еще темы с ответами:

Как правильно использовать цикл? - C++
Задача с экспонентами. Нужно найти следующее: ...

Как правильно использовать потоки? - C++
Тему про потоки прочёл. Так же ознакомился с классом mutex. И вот есть некий вопрос: как правильно использовать синхронизацию потоков в...

Как правильно использовать map? - C++
Свой код писал по примеру cut. Вот, что есть, и показываю скрин екхепшана, который выбрасывается. Кто-нибудь может помочь разобраться...

Как правильно использовать thread? - C++
Не могу понять как удалить поток thread во время его выполнения. То есть у меня есть поток в которым зацикливание (ниже в коду увидите) и в...


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

Или воспользуйтесь поиском по форуму:
Yandex
Объявления
02.09.2016, 16:40
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru