Loading...
Jovanna avatar Jovanna 186 Точки

C++ Advanced, 09_02_IDs - записване на един тип обект в друг тип

Здравейте,

какви са начините да се запише един тип обект в друг тип,  пробвах няколко варианта, но не работи нито един и се чудя защо:

1/ без онаследяване, независим обект HasId само с едно поле int id, слайсвaм Company към него: защото ми трябва само първото поле: 

   HasId* operator=(Company* c) {
        return (HasId*)c;    
2/     //VER.2
        //this->id = c->getId();
        //return this;    
    }

   Или 3/   HasId поле:    int id;
                  Ctor:              HasId(Company * c) : id(c->getId()) {}    И с един гетер за полето.

4/ако пробвам HasId да е базов и да се онаследява от Company, в конструктора й няма инициализация на базов клас, следователно тя няма базов клас, няма как да стане по този начин; Същото важи и за .HasInfo;

5/ ако HasInfo наследява Company, и HasId наследява Company,  се инициализират от main() е чрез = c, което е copy-elision тук и би трябвало да е така ctor-a:   

class HasId : public Company { public: int id;
    HasId() : Company() {}
    HasId(Company* c) : Company(c->id, c->name, c->employees), id(id) {}

Явно нещо бъркам? 

Поздрави!

Тагове:
0
C++ Programming 06/11/2018 16:19:17
MartinBG avatar MartinBG 4803 Точки
Best Answer

При примитивните типове има кастване, което може да е безопасно (без загуба на информация и предвидимо като резултат) - напр. целочислени типове към булев тип или към числа с плаваща запетая, int към long и т.н.. или рисково - нап. long към int, double към целочислен тип и т.н.

При обектите кастването има смисъл само между типове, които са свързани чрез наследяване. Всичко друго си е недефинирано поведение и гаранция за проблеми.

Слайсване може да има само от обект от наследяващ клас към базов тип, т.е. вариант №1 автоматично отпада.

Вариант №2 не го разбирам.

Вариант №3 също не ми е много ясен - HasId е самостоятелен клас който "живее" независимо от Company? Как се осъществява връзката между две инстанции от двата класа? Възможно е Company да има поле HasId (т.е. да изполваш composition), но самото име на HasId класа не ти ли се струва странно за нещо, от което мислиш да вдигаш обекти (Id би било по-добро име за такъв клас)?

Вариант №4 - В конструкторите на наследяващите класове не винаги се налага да се викат конструкторите на базовите такива. В конкретната задача нищо не пречи Company да наследява HasId и HasInfo без да се налага изричното извикване на конструкторите им, стига тези базови класове да нямат полета, които да инициализират през конструкторите си (т.е. да имат конструктори, различни от дефолтните).

Вариант #5 - Няма логика HasId и HasInfo, които са с доста базово предназначение, съдейки по имената им, да наследяват конкретно нещо като Company. И въобще, при наследяването връзката между наследяващия и базовия клас трябва да е от тип is a. Няма как HasId да е Company. Company може да има Id, т.е. може да използваме композиция, но отново името на този клас - HasId, ни подсказва нещо друго.

Името говори не за обект, а за свойство. Свойствата в OOP обикновенно се описват в т.н. интерфейси и точно това се очаква да бъдат HasId и HasInfo в тази задача.

1
06/11/2018 02:55:21
Jovanna avatar Jovanna 186 Точки

Благодаря за отговора, но все още не иска да се компилира:  class Company : public HasId, public HasInfo { ... }

struct HasInfo {
    virtual std::string getInfo() const = 0;
    virtual ~HasInfo() {};
};

struct HasId  {    
    virtual int getId() const = 0;
    virtual ~HasId() {};
};

т.е., като интерфейси, няма как да им се извлече информацията, ако нямат свои полета за нея, то те нямат и информация и полета като интерфейси. Вариант да им сложим по едно поле за информацията ( id-то  на HasId  и  info на HasInfo), тогава няма да са интерфейси, а абстрактни класове, и класа Company, ще трябва да инициализира в ctora си и техните сtor-и, а няма такава инициализация.

Също, принципно, може ли един интерфейс да наследява друг /или други (т.е., да се "окрупняват" в клас-интерфейс-наследяващ други интерфейси)?  Ако да, тогава в тази задача ще можем да направим Company да наследява HasInfo, а HasInfo да наследява HasId?

И по въпрос 1/ от предишния ми пост: Защо да не може да се кастне  ако са различни обекти които не се наследяват, нали в С++ може всичко? Примерно клас Id с едно поле int id;   и  клас Company с 3 полета както е в задачата (два независими класа):  ако създадем нов пойнтер от тип Id   и го насочим към пойнтер тип Company,  самия тип на пойнтера Id няма ли да си "вземе" само int полето (в случая сме сигурни че е първото поле в паметта и започва от където сочи пойнтер Company)  ako изпринтим Id дереференциран, или с getId() на клас Id , няма ли да се изпринти само първото поле от Company обекта?  

0
06/11/2018 15:57:01
MartinBG avatar MartinBG 4803 Точки

Каква е компилационната грешка?

Виртуалните методи от интерфейсите трябва да бъдат имплементирани в наследяващия клас. Нужните полета (id) също може да са дефинирани в класа (но това не е задължително; от гледна точка на инерфейса, той се интересува единствено дали наследяващия клас му имплементира методите, а не как го прави и какви данни използва):

class Company : public HasId, public HasInfo {
private:
  int id;
//...
public:
//...
  int getId() const override;

  std::string getInfo() const override;

};

 

Да, интерфейсите могат да бъдат наследявани, интерфейс може да наследи дори повече от един интерфейс, но това рядко е добра идея, защото интерфейсите описват конкретно поведение. "Сливането" на интерфейси по-често се прави в някой базов/абстрактен клас, като там може да се зададе и дефолтна имплементация на някои от методите.

 

...Company да наследява HasInfo, а HasInfo да наследява HasId

По-горе ти споменах за основното правило за връзката (is-a) между класовете при наследяване в ООП. Това правило ще е нарушено, ако приложиш такова наследяване.

 

C++ наистина дава много голяма свобода, но това не означава, че всичко което е позволено е и безопасно. Това, което си дала като пример почива на предположението, че полетата на обекта в паметта ще са разположени по точно определен начин. Дори ако това е стандартизирано в езика (не съм сигурен, но може да е така), всяка промяна по структурата на някой от двата класа ще счупи тази логика.

1
Jovanna avatar Jovanna 186 Точки

Компилационната  и на двата реда е, че не може да се използва стойност от тип Company за да се инициализира стойност от тип HasId / HasInfo, ето кода: https://pastebin.com/1HJKLEz6

А по въпроса за чисто виртуалните класове-интерфейси, от които в тази задача имаме два: как се извлича от пойнтер към Company, пренасочен към пойнтер от тип HasId  / HasInfo, съответната информация , като класовете HasId  / HasInfo имат само по един чисто виртуален метод?  Т.е., в паметта към какво сочи пойнтер HasId, чийто клас е чисто виртуален, интерфейсен?

Поздрави!

пренаписах кода: пак същите 2 грешки, не дава да се инициализира/ присвои от различните типове:

https://pastebin.com/BQQjCrPQ

0
06/11/2018 19:59:03
kolioi avatar kolioi 641 Точки

Е, това няма как да се компилира. Трябва да дефинираш HasId и HasInfo преди Company или поне да ги декларираш.

На другия ти въпрос - това трябва да сте го учили на лекциите (аз не вземам курса и нямам представа какво учите).

1
Jovanna avatar Jovanna 186 Точки

Благодаря!

Класовете не са ли с еднаква видимост в глобалното?

0
MartinBG avatar MartinBG 4803 Точки

Не.

В C++ няма хойстинг. :)

Променливи, функции и класове са видими след като бъдат деклрарирани/инклуднати.
 

Може би се бъркаш с видимостта на методите в даден клас - там реда на обявяването и извикването в рамките на класа е без значение.

1
06/11/2018 20:20:58
Можем ли да използваме бисквитки?
Ние използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Можете да се съгласите с всички или част от тях.
Назад
Функционални
Използваме бисквитки и подобни технологии, за да предоставим нашите услуги. Използваме „сесийни“ бисквитки, за да Ви идентифицираме временно. Те се пазят само по време на активната употреба на услугите ни. След излизане от приложението, затваряне на браузъра или мобилното устройство, данните се трият. Използваме бисквитки, за да предоставим опцията „Запомни Ме“, която Ви позволява да използвате нашите услуги без да предоставяте потребителско име и парола. Допълнително е възможно да използваме бисквитки за да съхраняваме различни малки настройки, като избор на езика, позиции на менюта и персонализирано съдържание. Използваме бисквитки и за измерване на маркетинговите ни усилия.
Рекламни
Използваме бисквитки, за да измерваме маркетинг ефективността ни, броене на посещения, както и за проследяването дали дадено електронно писмо е било отворено.