LINUX.ORG.RU

[C++,protected inheritance,тупняк] Преобразование к базовому классу не удаётся


0

1

При компиляции этого чудовищно сложного кода возникает ошибка: error: 'Base' is an inaccessible base of 'Derived'

#include <iostream>
//-------------------------------------------------------------------------
struct Base
{
};
//-------------------------------------------------------------------------
struct Derived : protected Base
{
 operator Base* ()
 { return static_cast<Base*>(this); }
 Derived* operator & ()
 { return this; }
};
//-------------------------------------------------------------------------
int main(int argc,char** argv)
{
 Derived derived;
 Base* pbase = static_cast<Base*>(&derived);
 return 0;
}
Но если сделать следующий идиотизм:
struct Derived : protected Base
{
 Derived& operator & ()
 { return *this; }
};
то ошибки чудесным образом не станет. Разумеется, корректно работать всё это не будет. Какой оператор/функцию нужно выставить в public, чтобы первоначальный вариант собирался?

Ответ на: комментарий от one_more_hokum

Честно говоря, понятия не имею.

Лучше расскажи, зачем тебе такое извращение понадобилось. Что мешает по-человечески унаследовать Base как public?

geekless ★★ ()
Ответ на: комментарий от geekless

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

one_more_hokum ★★★ ()
Ответ на: комментарий от one_more_hokum

Глянул внимательнее код.

Понятно, почему первый вариант не работает:

Ты пытаешься скастовать Derived* -> Base*. А у тебя объявлен operator() для приведения Derived -> Base*.

Во втором варианте ты перегружаешь оператор получения указателя в качестве указателя возвращаешь тип Derived. Поэтому каст Derived -> Base* в выражении static_cast<Base*>(&derived) проходит успешно.

Вывод: лютые костыли. Используй паблик наследование и не выпендивайся.

geekless ★★ ()
Ответ на: комментарий от one_more_hokum

> У базового класса довольно богатый функционал, который хоцца почти весь запереть от потенциального пользователя производного класса.

Опять не понятно: зачем.

Объяви интерфейсный класс, унаследуй от него класс с «богатым функционалом», пользователю дай только хидер для интерфейсного класса и фабрику.

geekless ★★ ()
Ответ на: комментарий от one_more_hokum

Всегда ваш, К.О.

Как это делается по уму?

#include <iostream>

struct Foo {
public:
    virtual void pew() {
        std::cout << "Foo::pew" << std::endl;
    }
};

struct Bar: protected Foo {
    Foo* basePtr() {
        return this;
    }

protected:
    virtual void  pew() {
        std::cout << "Bar::pew" << std::endl;
    }
};

int main (int argc, char *argv[])
{
    Bar *b = new Bar;
    Foo *f = b->basePtr();
    f->pew();
    return 0;
}
yoghurt ★★★★★ ()

Я так понимаю что private это наследование реализации, а public - наследование интерфейса. А какой смысл в защищенном наследовании?

Absurd ★★★ ()
Ответ на: комментарий от Absurd

> А какой смысл в защищенном наследовании?

Наследник может пользоваться public-интерфейсом базового класса, но наружу отдавать не будет.

one_more_hokum ★★★ ()

Может быть стоит определить для себя следующий момент:
Доступен ли функционал класса Base для пользователя класса Derived.
Если нет - тогда компилятор правильно ругается, если да - тогда надо наследоваться как public. Если да, но не всегда - значит вы хотите странного.
Поясню. Если в любом случае можно достучаться до Base имея на руках только Derived (пусть даже и через одно место под названием static_cast), то зачем тогда его прятать?

trex6 ★★★★★ ()
Ответ на: комментарий от trex6

Да, пожалуй, сама постановка задачи уже была некорректной у меня. Вопрос закрываю.

one_more_hokum ★★★ ()

а вот clang соглашается

уж не баг ли это в g++?

P.S. вот тоже с более привычными отступами

#include <iostream>
//-------------------------------------------------------------------------
struct Base
{
};
//-------------------------------------------------------------------------
struct Derived : protected Base
{
    operator Base* () {
        return static_cast<Base*>(this);
    }
    Derived* operator & () {
        return this;
    }
};
//-------------------------------------------------------------------------
int main(int argc,char** argv)
{
    Derived derived;
    Base* pbase = static_cast<Base*>(&derived);
    return 0;
}

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

AFAIK по стандарту в случае приватного наследования Derived *не является* производным классом от Base, и static_cast работать не должен, только C-style cast или reinterpret_cast. Для protected-наследования по идее должно быть то же самое.

Absurd ★★★ ()
Ответ на: комментарий от trex6

> значит вы хотите странного.

нет (точнее, не факт)

у Derived может быть например потомок, который все разруливает и разрешает себя кастить обратно к Base; отношение «потомок» должно быть транзитивно, т.е. и Derived должен иметь такое право

или вот например template<class T> class Derived: protected Base {...много всего...} и в каких-то редких случаях (например Т is POD) надо таки опять разрешить кастить наверх

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от one_more_hokum

>У базового класса довольно богатый функционал, который хоцца почти весь запереть от потенциального пользователя производного класса.

аггрегирование, не?

Absurd ★★★ ()
Ответ на: комментарий от Absurd

AFAIK в случае приватного наследования Derived не является производным классом от Base только снаружи класса, а внутри — является

с чем согласны оба компилятора: return static_cast<Base*>(this) проходит без ошибок

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

Вообще странно получается. Получаем указатель на Derived (&derived), этим занимается переопределённый его оператор, и пытаемся привести этот указатель к Base*. А приведение есть только Derived => Base*. Так?

one_more_hokum ★★★ ()
Ответ на: комментарий от Absurd

> аггрегирование, не?

в говнояве да

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

теперь (изнутри производного) мы можем вызывать любую функцию базового класса, и поле х при этом не потеряется

а что делать при агрегировании?

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

>AFAIK в случае приватного наследования Derived не является производным классом от Base только снаружи класса, а внутри — является

Внутри класса можно вызывать паблик-методы приватного родителя или читать/писать писать его паблик-поля. Но кастить Derived к Base нельзя во всех контекстах, т.к Derived это *не потомок* Base.

Absurd ★★★ ()
Ответ на: комментарий от Absurd

> аггрегирование, не?

Мне нужно будет передавать объекты производного класса в функции, принимающие указатель на базовый. Функции не мои, библиотечные, поправить их низзя. Агрегирование здесь пройдёт по тем же граблям.

one_more_hokum ★★★ ()
Ответ на: комментарий от one_more_hokum

> А ты кем проверял?

clang version 1.1 (Debian 2.7-3)
Target: i386-pc-linux-gnu
Thread model: posix

...

clang is already the newest version.

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от one_more_hokum

clang и на такое соглашается

#include <iostream>
//-------------------------------------------------------------------------
struct Base
{
};
//-------------------------------------------------------------------------
struct Derived : protected Base
{
};
//-------------------------------------------------------------------------
int main(int argc,char** argv)
{
    Derived derived;
    Base* pbase = static_cast<Base*>(&derived);
    return 0;
}
www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от Absurd

Но кастить Derived к Base нельзя во всех контекстах, т.к Derived это *не потомок* Base.

меньше на яве программировать надо

#include <iostream>

struct Base
{
};
struct Derived : protected Base
{
    Base* test () {
        return this;
    }
};
int main(int argc,char** argv)
{
    return 0;
}
www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

Еще раз.
1. Либо класс позволяет использовать себя как Base, и тогда надо наследоваться от Base как public.
2. Класс не позволяет использовать себя как Base, тогда все работает правильно.

Вы же предлагаете следующее: класс иногда позволяет использовать себя как Base, иногда нет. Это уже не класс получается, а какая-то девушка легкого поведения.
Я привык считать такие вещи ошибками в архитектуре приложения.

trex6 ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

>> Но кастить Derived к Base нельзя во всех контекстах, т.к Derived это *не потомок* Base.

меньше на яве программировать надо

Если класс Derived внутри себя приватно *содержит реализацию* Base, то вполне логично иметь возможность передавать ее во внешние функции через this. В данном коде же мы имеем abstraction leak, только и всего.

Absurd ★★★ ()
Ответ на: комментарий от trex6

> Вы же предлагаете следующее: класс иногда позволяет использовать себя как Base, иногда нет. Это уже не класс получается, а какая-то девушка легкого поведения. Я привык считать такие вещи ошибками в архитектуре приложения.

емнип еще страуструп приводил пример: производный класс (на веб-сервере) может быть непроверенными данными с юзеровской формы, а родительский — *теми же* самыми данными, но уже проверенными

тогда вполне возможна ситация «единственная функция производного класса возвращает родительский» (правда после проверки)

www_linux_org_ru ★★★★★ ()
Ответ на: комментарий от www_linux_org_ru

Это вообще очень банальное проектирование иерархии классов. Проверенные данные запроса *являются подмножеством* просто данных запроса. Поэтому VerifiedReqData должны быть унаследованы от ReqData а не наоборот.

Absurd ★★★ ()
Ответ на: комментарий от www_linux_org_ru

>страуструп приводил пример
С удовольствием бы глянул на этот пример подробно. Если вспомните в какой книге и в каком разделе (либо ссылку на статью) - буду благодарен за эту информацию.

В описанной вами ситуации логичнее сделать наследование в обратную сторону: проверенный класс является потомком непроверенного и содержит конструктор, который позволяет создать экземпляр проверенного класса на основе экземпляра непроверенного. Этот же конструктор может проверять введенные данные на соответствие бизнес-логике приложения.

trex6 ★★★★★ ()
Ответ на: комментарий от trex6

>Если вспомните в какой книге и в каком разделе

мне самому интересно стало найти его

а после этого продолжим

www_linux_org_ru ★★★★★ ()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.