1 Востаннє редагувалося leofun01 (10.06.2016 15:15:48)

Тема: Error C2555: overriding virtual function return type differs...

Хочу поділитися сюрпризами, які видає Visual C++ 2013, і за одно спитати, якого лисого вона так робить.

Спочатку постановка задачі, яку я виконую на C++:
Створити 3 класи вузлів, де:
одні будуть містити вказівник на наступний елемент;
другі будуть містити вказівники на наступний і попередній елементи;
треті будуть містити ті самі вказівники що і другі, але не будуть дорівнювати 0 (NULL, nullptr), тобто по замовчуванню вказуватимуть на себе.
+ Кожен з вузлів має містити якісь дані (але це не має відношення до проблеми).

Ну я, як і всякий нормальний програміст, почав ліпити з цих 3-х класів ієрархію (в прикріпленому архіві оригінальні файли мого рішення). В кожному класі я створив публічні віртуальні методи, які повертають вказівники на інші елементи, і типи цих вказівників, є такими ж як клас, в якому ці методи описані.
Наслідування вирішив зробити не публічним (public), і навіть не захищеним (protected), а закритим (private), щоб ніхто, крім безпосереднього нащадка, не мав доступу до методів, які працюють з вказівниками типу предка.
Також поставив const після імен функцій, які не змінюють значення полів класів.
Ось дуже спрощений приклад, який не позбавлений суті проблеми:

class Node {
private:
    Node *next;
public:
    virtual Node *const GetNext() const { return next; }
};
class DualNode : private Node {
private:
    DualNode *prev;
public:
    virtual DualNode *const GetNext() const { return static_cast<DualNode *>(Node::GetNext()); }
    virtual DualNode *const GetPrev() const { return prev; }
};
class ExtendedNode : private DualNode {
public:
    virtual ExtendedNode *const GetNext() const { return static_cast<ExtendedNode *>(DualNode::GetNext()); }
    virtual ExtendedNode *const GetPrev() const { return static_cast<ExtendedNode *>(DualNode::GetPrev()); }
};

Cпробував скомпілювати це добро і отримав:
error C2555: 'ExtendedNode::GetNext': overriding virtual function return type differs and is not covariant from 'DualNode::GetNext'.
Я навіть не знаю як сформулювати питання, це ж просто "WTF?".
Як мені залишити private-наслідування, virtual-const-функції, і позбутися помилки ?

Покищо розглядаю 2 варіанти, які компілюються, але мене не задовільняють:

1) Зробити наслідування protected:

class Node {
private:
    Node *next;
public:
    virtual Node *const GetNext() const { return next; }
};
class DualNode : protected Node {
private:
    DualNode *prev;
public:
    virtual DualNode *const GetNext() const { return static_cast<DualNode *>(Node::GetNext()); }
    virtual DualNode *const GetPrev() const { return prev; }
};
class ExtendedNode : protected DualNode {
public:
    virtual ExtendedNode *const GetNext() const { return static_cast<ExtendedNode *>(DualNode::GetNext()); }
    virtual ExtendedNode *const GetPrev() const { return static_cast<ExtendedNode *>(DualNode::GetPrev()); }
};

2) Видалити const із ExtendedNode::GetNext()

class Node {
private:
    Node *next;
public:
    virtual Node *const GetNext() const { return next; }
};
class DualNode : private Node {
private:
    DualNode *prev;
public:
    virtual DualNode *const GetNext() const { return static_cast<DualNode *>(Node::GetNext()); }
    virtual DualNode *const GetPrev() const { return prev; }
};
class ExtendedNode : private DualNode {
public:
    virtual ExtendedNode *const GetNext() { return static_cast<ExtendedNode *>(DualNode::GetNext()); }
    virtual ExtendedNode *const GetPrev() const { return static_cast<ExtendedNode *>(DualNode::GetPrev()); }
};

Обидва варіанти змушують мене плакати :(.

Post's attachments

NodeTemplates.zip 4 kb, 266 downloads since 2016-06-10 

2

Re: Error C2555: overriding virtual function return type differs...

Сенс віртуальних функцій в тому, що вони мають абсолютно однаковий інтерфейс, але різняться реалізацією.
А у вас ці функції повертають різні типи -  Node *, DualNode * та ExtendedNode *. Замініть все на Node*. І не використовуйте _cast, тим більше - static_cast. Поліморфізм для того і придумали, щоб не кастувати.

Подякували: leofun011

3 Востаннє редагувалося leofun01 (10.06.2016 14:55:09)

Re: Error C2555: overriding virtual function return type differs...

koala написав:

Сенс віртуальних функцій в тому, що вони мають абсолютно однаковий інтерфейс, але різняться реалізацією.
А у вас ці функції повертають різні типи -  Node *, DualNode * та ExtendedNode *.

Якби мої методи повертали оригінали вказівників (використовуючи &), як тут:

class Node {
private:
    Node *next;
public:
    virtual Node *const &GetNext() const { return next; }
};
class DualNode : private Node {
private:
    DualNode *prev;
public:
    virtual DualNode *const &GetNext() const { return static_cast<DualNode *>(Node::GetNext()); }
    virtual DualNode *const &GetPrev() const { return prev; }
};
class ExtendedNode : private DualNode {
public:
    virtual ExtendedNode *const &GetNext() const { return static_cast<ExtendedNode *>(DualNode::GetNext()); }
    virtual ExtendedNode *const &GetPrev() const { return static_cast<ExtendedNode *>(DualNode::GetPrev()); }
};

тоді так. Але вони повертають копії.
Крім того хочу звернути вашу увагу на те, що компілятор не свариться на методи DualNode::GetNext() і ExtendedNode::GetPrev() (з коду наведеного в першому повідомленні), хоча якщо слідувати за вашою логікою, то мав би.

koala написав:

Замініть все на Node*. І не використовуйте _cast, тим більше - static_cast. Поліморфізм для того і придумали, щоб не кастувати.

Але вся фішка якраз в тому, щоб клієнт не парився з моїм кодом і не приводив типи самотужки. Та все ж обдумаю вашу пропозицію.

Також рекомендую переглянути файли з архіву (їх всього 3), бо там випливають всякі нюанси, які не дають мені так просто переробити типи.

4

Re: Error C2555: overriding virtual function return type differs...

Не поясните таємний сенс оцього:

struct DualNode : protected Node<T>
{
...
virtual T const &GetData() const { return Node<T>::GetData(); }

Тобто створюється віртуальна функція в DualNode, яка... викликає батьківську віртуальну функцію? Нащо?
І чому вони структури, якщо, вочевидь, мають бути класи?

Подякували: leofun011

5

Re: Error C2555: overriding virtual function return type differs...

Прихований текст

ОТО НАРОБИЛИ ВЖЕ!!!

Подякували: leofun011

6 Востаннє редагувалося leofun01 (10.06.2016 15:31:54)

Re: Error C2555: overriding virtual function return type differs...

koala написав:

Не поясните таємний сенс оцього:

struct DualNode : protected Node<T>
{
...
virtual T const &GetData() const { return Node<T>::GetData(); }

Тобто створюється віртуальна функція в DualNode, яка... викликає батьківську віртуальну функцію? Нащо?

Метод GetData() має бути public для клієнта, а успадковується він як protected (із-за DualNode : protected Node<T>). Якби я його не описав знову, то інші класи його би не бачили. А вказання класу (Node<T>::), з якого викликається GetData() предка, дозволяє уникнути невизначеності в реалізації.

koala написав:

І чому вони структури, якщо, вочевидь, мають бути класи?

В моїх програмах вони виглядають більше як вмістилища даних ніж як обробники.
Крім того, наскільки я знаю, в C++ різниця між class і struct лише в модифікаторі доступу по замовчуванню.
Хоча... так, їх можна було зробити класами, в цьому випадку це не принципово.

7

Re: Error C2555: overriding virtual function return type differs...

leofun01

Метод GetData() має бути public для клієнта, а успадковується він як protected (із-за DualNode : protected Node<T>). Якби я його не описав знову, то інші класи його би не бачили. А вказання класу (Node<T>::), з якого викликається GetData() предка, дозволяє уникнути невизначеності в реалізації.

Я не впевнений, але мені здається що тут було б доцільно NVI застосувати. Взагалі, ви якось по-збоченному використовуєте поліморфізм :)

Подякували: leofun011

8

Re: Error C2555: overriding virtual function return type differs...

А чому ці функції мають бути віртуальними?

Подякували: leofun011

9 Востаннє редагувалося leofun01 (10.06.2016 17:36:01)

Re: Error C2555: overriding virtual function return type differs...

koala написав:

А чому ці функції мають бути віртуальними?

Щоб їх можна було перевизначити, звичайно. Вузли мають різну поведінку зв'язування.
Якщо хтось візьме DualNode і запхає його в Node, то поводитись він має як DualNode.
Це якщо про Get/Set-Next/Prev.

Ну і поведінка відправки даних теж має бути загалом різною.

10 Востаннє редагувалося -=ЮрА=- (10.06.2016 18:24:28)

Re: Error C2555: overriding virtual function return type differs...

leofun01 якщо йдуть константні методи, то де зняття константності через const_cast? Далі - віртуальні класи і використовуєте static_cast та він що завгодно по поінтеру може закастувати, вам же потрібно тільки валідне кастування - його забезпечує лише dynamic_cast. Останнє private - наслідування - помилка дизайна при проектуванні, виключайте нафік.

#include <iostream>
using namespace std;

class Node {
private:
    Node *next;
public:
    virtual const Node * GetNext() const { return next; }
};
class DualNode : public Node {
private:
    DualNode *prev;
public:
    virtual const DualNode * GetNext() const{ return dynamic_cast< DualNode *>(const_cast< Node *>(Node::GetNext())); }
    virtual const DualNode * GetPrev() const{ return prev; }
};
class ExtendedNode : public DualNode {
public:
    virtual const ExtendedNode * GetNext() const{ return dynamic_cast< ExtendedNode *>(const_cast< DualNode *>(DualNode::GetNext())); }
    virtual const ExtendedNode * GetPrev() const{ return dynamic_cast< ExtendedNode *>(const_cast< DualNode *>(DualNode::GetPrev())); }
};

int main(){
    ExtendedNode extNode;
    return 0;
}

No errors or program output.

http://codepad.org/5VB6dBY6

Подякували: leofun011

11

Re: Error C2555: overriding virtual function return type differs...

Arete написав:
leofun01 написав:

Метод GetData() має бути public для клієнта, а успадковується він як protected (із-за DualNode : protected Node<T>). Якби я його не описав знову, то інші класи його би не бачили. А вказання класу (Node<T>::), з якого викликається GetData() предка, дозволяє уникнути невизначеності в реалізації.

Я не впевнений, але мені здається що тут було б доцільно NVI застосувати. Взагалі, ви якось по-збоченному використовуєте поліморфізм :)

NVI - благородна задумка, але мені майже нічого не вдалося з нього вижати.
Зато появився новий файл (в архіві).

Post's attachments

NodeTemplates2.zip 4.61 kb, 262 downloads since 2016-06-10 

12 Востаннє редагувалося leofun01 (10.06.2016 21:12:05)

Re: Error C2555: overriding virtual function return type differs...

-=ЮрА=- написав:

Далі - віртуальні класи і використовуєте static_cast та він що завгодно по поінтеру може закастувати, вам же потрібно тільки валідне кастування - його забезпечує лише dynamic_cast.

Але dynamic_cast працює під час виконання програми (в рантаймі), а static_cast при компіляції, один раз встановив і під час виконання не замислюється над тим, а значить і працює швидше. Крім того dynamic_cast потрібен, коли не має гарантії, що тип який приводиться, взагалі можна привести. Тому і використовував static_cast.

-=ЮрА=- написав:

Останнє private - наслідування - помилка дизайна при проектуванні, виключайте нафік.

Дивно, мені воно як раз здалося дуже потрібним.

-=ЮрА=- написав:

leofun01 якщо йдуть константні методи, то де зняття константності через const_cast?

VisualStudio помилок щодо цього не виписувала, от я і не робив const_cast.

13 Востаннє редагувалося -=ЮрА=- (12.06.2016 15:29:51)

Re: Error C2555: overriding virtual function return type differs...

Але dynamic_cast працює під час виконання програми (в рантаймі), а static_cast при компіляції, один раз встановив і під час виконання не замислюється над тим, а значить і працює швидше. Крім того dynamic_cast потрібен, коли не має гарантії, що тип який приводиться, взагалі можна привести. Тому і використовував static_cast.

- тоді раджу проаналізувати код нижче, ну як допоміг статік каст, чи може нашкодив?Я і писав про те що все надійніше перевіряти у рантаймі, статіком первіряють те що не підходіть під динамічне віділення, а наява поінтеров у ваших класах вже говорить про те що виділення будут динамічними, ніякий статік вам там не допоможе.

#include <iostream>
using namespace std;
 
class Node {
private:
    Node *next;
public:
    virtual const Node * GetNext() const { return next; }
};
class DualNode : public Node {
private:
    DualNode *prev;
public:
    virtual const DualNode * GetNext() const{ return dynamic_cast< DualNode *>(const_cast< Node *>(Node::GetNext())); }
    virtual const DualNode * GetPrev() const{ return prev; }
};

int main(){
    Node *nodeArr[2] = {0};
    long *pointer    = new long;
    nodeArr[0] = new Node;
    nodeArr[1] = static_cast< Node *>((Node *)pointer);
    return 0;
}

http://codepad.org/hiVWiPpp

VisualStudio помилок щодо цього не виписувала, от я і не робив const_cast.

- VisualStudio навіть остання має деякі відмінності від компілятору у стандарті, я подаю лінки на те що більш менш відповідає С++0х

Останнє private - наслідування - помилка дизайна при проектуванні, виключайте нафік.
Дивно, мені воно як раз здалося дуже потрібним.

- я вам не обсудити писав, а писав що треба виключати, почитайте ресурси о приватному наслідуванні, певен зрозумієте.

Подякували: leofun011

14 Востаннє редагувалося leofun01 (13.06.2016 01:24:53)

Re: Error C2555: overriding virtual function return type differs...

-=ЮрА=- написав:
    Node *nodeArr[2] = {0};
    long *pointer    = new long;
    nodeArr[0] = new Node;
    nodeArr[1] = static_cast< Node *>((Node *)pointer);

Якщо клієнт буде таке витворяти, то це вже його особисті проблеми. А мій код не містить подібних випадків.

-=ЮрА=- написав:
leofun01 написав:
-=ЮрА=- написав:

Останнє private - наслідування - помилка дизайна при проектуванні, виключайте нафік.

Дивно, мені воно як раз здалося дуже потрібним.

- я вам не обсудити писав, а писав що треба виключати, почитайте ресурси о приватному наслідуванні, певен зрозумієте.

Може я щось не те читаю ...
tutorialspoint.com (cpp_inheritance)
cplusplus.com (tutorial/inheritance)
c2.com (PrivateInheritance)
cplusplus.com (example)
gotw.ca (example)
cprogramming.com (example)
http://cppforeach.wordpress.com/2008/05/19/around_access_modifiers/
В двох словах: ніде не бачив, щоб хтось наводив аргументи проти приватного наслідування.

upd: Здається я дуже близько до рішення моєї проблеми.

15 Востаннє редагувалося -=ЮрА=- (13.06.2016 20:27:07)

Re: Error C2555: overriding virtual function return type differs...

leofun01 написав:

Якщо клієнт буде таке витворяти, то це вже його особисті проблеми. А мій код не містить подібних випадків.

- та ні взагалі то у контейнерах розробник повинен передбачити усе, у вас там у коді я бачив додавання данних через поінтер якийсь SetData(Node *val) - щось таке, а якщо користувач "всуне" туде якусь фігню?Чому програма повинна вилітати тільки тому що розробник не захотів динамічно кастувати?А якщо совтина дуже важлива, що такий фактор як тупий юзер(чи кодер, що буде юзати ваші класи далі) повинен впливати на стабільність?

leofun01 написав:

Може я щось не те читаю ...

- не треба читати все що пишуть на стінах та парканах. Уявіть собі ситуацію коли знадобиться доступ до якихось приватних змінних чи методів у класах потомках, що буде робити програміст - ліпити нові set/get у базовий клас, а якщо класс зібраний під лібу, тобто декларації є а код вже у lib файлі? Я дуже часто стикався з таким. Найкращій варіант public наслідування - все зостається таким яким воно є.

Подякували: leofun011

16 Востаннє редагувалося -=ЮрА=- (13.06.2016 20:26:37)

Re: Error C2555: overriding virtual function return type differs...

upd: Здається я дуже близько до рішення моєї проблеми.

- так а яка у вас проблема? Я гадав що після мого поста 10 ви темплейти виложили для користувачів, а виявляється в ще у пошуках відповіді, будь ласка введіть у курс що не так? Деякий досвід кодингу у мене точно є - допоможу.

Подякували: leofun011

17 Востаннє редагувалося leofun01 (13.06.2016 23:49:59)

Re: Error C2555: overriding virtual function return type differs...

-=ЮрА=- написав:

- так а яка у вас проблема? Я гадав що після мого поста 10 ви темплейти виложили для користувачів, а виявляється в ще у пошуках відповіді, будь ласка введіть у курс що не так? Деякий досвід кодингу у мене точно є - допоможу.

Ну з самого початку проблема звучала так:
Як мені залишити private-наслідування, virtual-const-функції, і позбутися помилки ?
Коли я писав, що рішення близько, то думав, що мені допоможе using. Наприклад:

class Node {
private:
    Node *next;
public:
    virtual Node *const GetNext() const { return next; }
};
class DualNode : private Node {
private:
    DualNode *prev;
protected:
    using Node::GetNext;
public:
    virtual DualNode *const GetNext() const { return static_cast<DualNode *>(Node::GetNext()); }
    virtual DualNode *const GetPrev() const { return prev; }
};
class ExtendedNode : private DualNode {
protected:
    using DualNode::GetNext;
    using DualNode::GetPrev;
public:
    virtual ExtendedNode *const GetNext() const { return static_cast<ExtendedNode *>(DualNode::GetNext()); }
    virtual ExtendedNode *const GetPrev() const { return static_cast<ExtendedNode *>(DualNode::GetPrev()); }
};

І в деяких випадках це дуже корисна штука, яка піднімає модифікатор доступу для вказаних полів і методів. Але в мене воно не дозволяло одночасно піднімати модифікатор з private до protected (або public) і при цьому перевизначати тіло методу.
Я думаю розробники просто недооцінили збоченість деяких програмістів (таких як я), ну і мабуть спростили собі життя за рахунок блокування перевизначення private-успадкованих методів.
Грець з ними, буду жити з protected-наслідуванням, воно принаймні компілюється.
Сподіваюсь я просто чогось не знаю або не розумію в розробці архітектури на C++, бо інакше мова С++ (Visual C++) - як мінімум недопрацьована.

18

Re: Error C2555: overriding virtual function return type differs...

using в іншому розділі означає, що ви змінюєте доступність метода, але не сам метод.
Перевизначення тіла означає, що ви змінюєте метод. Якщо в іншому розділі - то разом з доступністю.
Чого ви намагаєтеся досягти, одночасно використовуючи using і тіло методу - я не розумію.
Втім, я взагалі не розумію, чого ви тут намагаєтеся досягти, це, як на мене, виглядає "підставити в рівняння x+y=z числа 2,2 і 5, так щоб рівняння було істинним": тут або існує відповідь, або не існує, але на практиці від того користі нуль.

Подякували: leofun011

19 Востаннє редагувалося leofun01 (14.06.2016 17:34:37)

Re: Error C2555: overriding virtual function return type differs...

-=ЮрА=- написав:

у контейнерах розробник повинен передбачити усе, у вас там у коді я бачив додавання данних через поінтер якийсь SetData(Node *val) - щось таке, а якщо користувач "всуне" туде якусь фігню?Чому програма повинна вилітати тільки тому що розробник не захотів динамічно кастувати?

Ваша правда, я щойно знайшов місця, де static_cast - неприпустимий.
Знайшов в своєму коді кілька помилок у викликах конструкторів (вже виправив), підігнав код під Visual C++ 2010, і тепер маю робочу версію. Як автор цього коду, роздаю права на використання, Ви можете використовувати його як хочете, навіть в комерційних проектах. Хоча таке гівно мабуть нікому і не треба :D.

Post's attachments

NodeTemplates_3.zip 4.02 kb, 220 downloads since 2016-06-14