Russian Qt Forum

Программирование => С/C++ => Тема начата: Igors от Март 28, 2014, 16:01



Название: Вызов private virtual метода
Отправлено: Igors от Март 28, 2014, 16:01
Добрый день

Право, растерялся - если "private" то "извне" никак не вызвать. Однако простой пример ниже вызывает (печатается "А")
Код
C++ (Qt)
#include <stdio.h>
 
class A {
private:
virtual void Print( void ) { printf("A\n"); }
};
 
class B : public A {
public:
B( A & a ) : mA(a) {}
 
void Print( void ) { printf("B\n"); }
 
void Test( void )
{
B & b = static_cast <B &> (mA);
b.Print();
}
 
// data
A & mA;
};
 
int main(int argc, char *argv[])
{
A a;
B b(a);
b.Test();
 
return 0;
}
 
Please "ткните носиком" в статейки где это обсуждается


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 28, 2014, 16:11
Можно и короче
Код
C++ (Qt)
#include <stdio.h>
 
class A {
private:
virtual void Print( void ) { printf("A\n"); }
};
 
class B : public A {
public:
void Print( void ) { printf("B\n"); }
};
 
int main( void )
{
A a;
(static_cast <B &> (a)).Print();
 
return 0;
}
 


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 28, 2014, 16:20
Так вы же в наследнике (B) поменяли доступ на public.

Please "ткните носиком" в статейки где это обсуждается
Какие статейки, это описано в каждой книге, где автор только слышал про C++. :)


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 28, 2014, 16:58
Так вы же в наследнике (B) поменяли доступ на public.

Какие статейки, это описано в каждой книге, где автор только слышал про C++. :)
Неужели ???  Смотрим стандарт
Цитировать
If a class is declared to be a base class (clause 10) for another class using the public access specifier, the public members of the base class are accessible as public members of the derived class and protected members of the base class are accessible as protected members of the derived class.

If a class is declared to be a base class for another class using the protected access specifier, the public and protected members of the base class are accessible as protected members of the derived class.

If a class is declared to be a base class for another class using the private access specifier, the public and protected members of the base class are accessible as private members of the derived class106).
Короче - квалификатор наследования может урезать права доступа (к базовому классу) но никогда их не расширит.

В примере выше мы не сможем написать A::Print() в методе класса B


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 28, 2014, 17:09
Не сможете.
Но здесь другой эффект. Компилятор проверяет права доступа, только в момент компиляции.
И здесь он видит, что вы пытаетесь вызвать метод print для объекта B.
Во время компиляции он не знает, что это на самом деле объект A. А при исполнении в vtbl этого объекта будет указатель на метод print объекта A.

А про стандарт. Что то вы путаете или ваш стандарт. Насколько я помню, мы можем менять доступ произвольно. :)
Точнее вы читаете совсем про другое. :)


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 28, 2014, 17:52
А про стандарт. Что то вы путаете или ваш стандарт. Насколько я помню, мы можем менять доступ произвольно. :)
Точнее вы читаете совсем про другое. :)
Неужели так сложно признаться "забыл, попутал" (или просто промолчать) - ведь это всего-навсего знание справочника :) Вот еще
Цитировать
114) As specified previously in Clause 11, private members of a base class remain inaccessible even to derived classes unless friend declarations within the base class definition are used to grant access explicitly.

Не сможете.
Но здесь другой эффект. Компилятор проверяет права доступа, только в момент компиляции.
И здесь он видит, что вы пытаетесь вызвать метод print для объекта B.
Во время компиляции он не знает, что это на самом деле объект A. А при исполнении в vtbl этого объекта будет указатель на метод print объекта A.
В данном случае - прекрасно знает, "A" объявлен выше. Но не суть, мы можем вынести  это в ф-цию принимающую указатель/ссылку на A

Так что, выходит вызывать virtual private базового класса все-таки можно? Никогда не знал о таком. Или это все-таки некорректно  ???


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 28, 2014, 18:00
Неужели так сложно признаться "забыл, попутал" (или просто промолчать) - ведь это всего-навсего знание справочника :)
А почему я должен это признавать? Я ничего не забыл.
Тот текст который вы привели повествует совсем о другом. :)
Как вы это переводите? :)
Цитировать
If a class is declared to be a base class

При написании класса мы можем определить метод в любой секции, куда захотим. Компилятору будет все равно, он будет проверять возможность доступа к методам. Поэтому, вызвать в производном классе private-метод базового мы не сможем, так же как и сделать ему using.
В вашем примере, вы не пытались добраться до метода A::print. Для компилятора вы определили новый публичный метод B::print, который по сигнатуре полностью совпал с виртуальным A::print. Должен ли компилятор предупреждать об этом - не знаю. Доступные мне компиляторы этого не делают.

Вот пример, как легко меняется доступ в любую сторону:
Код
C++ (Qt)
// Здесь Print public
class A {
public:
       virtual void Print() { printf("A\n"); }
};
 
// Здесь мы его запротектили
class B : public A {
protected:
       virtual void Print() { printf("B\n"); A::Print(); }
};
 
// Здесь мы его опять достали в паблик
class C : public B {
public:
       virtual void Print() { printf("C\n"); B::Print(); }
};
 
 
int main( void )
{
       A a;
       B b;
       C c;
 
       a.Print();
//      b.Print();    << B::Print недоступен
       c.Print();
 
       return 0;
}
 
Напечатает:
Цитировать
A
C
B
A

Но не суть, мы можем вынести  это в ф-цию принимающую указатель/ссылку на A
Покажите, что вы имеете ввиду.

Так что, выходит вызывать virtual private базового класса все-таки можно? Никогда не знал о таком. Или это все-таки некорректно  ???
Конечно можно, даже не виртуальные можно, таких способов полно. Только наберите в гугле и вы утонете в информации. :)
Это все защита от ошибок, а не реальная защита от злоумышленников.


Название: Re: Вызов private virtual метода
Отправлено: Alex Custov от Март 28, 2014, 20:10
If a class is declared to be a base class (clause 10) for another class using the public access specifier, the public members of the base class are accessible as public members of the derived class and protected members of the base class are accessible as protected members of the derived class.

If a class is declared to be a base class for another class using the protected access specifier, the public and protected members of the base class are accessible as protected members of the derived class.

If a class is declared to be a base class for another class using the private access specifier, the public and protected members of the base class are accessible as private members of the derived class106).
Короче - квалификатор наследования может урезать права доступа (к базовому классу) но никогда их не расширит.

Здесь речь идёт о дефолтном поведении для наследуемых методов, которые в явном виде не описаны в классе-наследнике. Если вы переопределяете виртуальный метод, то доступ можете установить ему какой хотите.


Название: Re: Вызов private virtual метода
Отправлено: Akon от Март 29, 2014, 10:15
Цитировать
Так что, выходит вызывать virtual private базового класса все-таки можно? Никогда не знал о таком. Или это все-таки некорректно
Я никогда не пользовался такой возможностью, даже не думал об этом. имхо, это некорректность из разряда: "нельзя иметь поля в классе, кроме как private".

А невирт. ф-ии таким образом вызвать нельзя.

Есть такой паттерн дизайна, как NVI (non-virtual interface). Подробно описан в книге Саттера "Сложные задачи" (название не дословное). Ксати, в той же книге есть рассмотрение вопросов, касающихся доступа. Основная суть NVI - паблик виртуальная функция - это слишком жирно, поскольку она определяет как интерфейс, так и реализацию (дает возможность изменить поведение). Поэтому, данный паттерн регламентирует иметь паблик невиртуальную функцию в базовом классе (определяет интерфейс) и привейт виртуальные функции в наследниках (определяют поведение). Если необходимо из наследника вызывать виртуальную функцию базового класса, то используется протектэд вместо привэйт.

Если для нового кода следовать этому паттерну, то об этой возможности вы даже не задумаетесь.


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 29, 2014, 10:22
Если вы переопределяете виртуальный метод, то доступ можете установить ему какой хотите.
Для своего, переопределенного метода - конечно. Но private метод базового класса остается недоступным.
Код
C++ (Qt)
void MyFunc( const QSomeClass & a );
...
class QSоmeClass {
private:
 virtual void Method1( void );
 
Как мне вызвать QSomeClass::Method1 извне? Даже если protected (вместо private) - это часто капитальный геморрой. Надо переопределять класс (часто во многих местах), а главное - экземпляр QSomeClass может быть создан не моим кодом. Оказывается можно вызвать не создавая экземпляр

Но не суть, мы можем вынести  это в ф-цию принимающую указатель/ссылку на A
Покажите, что вы имеете ввиду.
Напр здесь компилятору ничего не известно кто такой "а" и он обязан использовать виртуальный механизм
Код
C++ (Qt)
void Test( A & a );


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 29, 2014, 10:30
Это все хаки.
Самый простой это сделать:
#define private public

Вы описываете другой хак (умолчим, что такое преобразование опасно), есть ещё другие.
Защита доступа в C++ сделана для защиты от ошибок, а не для защиты от злоумышленников. Поэтому, она простая и работает на этапе компиляции.

Лично я хаками не пользуюсь.


Название: Re: Вызов private virtual метода
Отправлено: Bepec от Март 29, 2014, 13:04
Читал статейку и пробовал.
По сути private и public это защита от ошибок программиста, не более. И зная структуру класса можно вызвать что угодно и когда угодно, имея лишь его указатель.

PS защита от дурака в действии.


Название: Re: Вызов private virtual метода
Отправлено: Alex Custov от Март 29, 2014, 14:16
По сути private и public это защита от ошибок программиста, не более. И зная структуру класса можно вызвать что угодно и когда угодно, имея лишь его указатель.

нет, public/private - это реализация одного из фундаментальных принципов, инкапсуляции. Но из-за того, что С++ решил быть расширенным Си, всё оказалось довольно криво и обходимо, как верно замечено про указатель на объект, даже в случае с PIMPL. Проблема только в том, что порядок полей и их смещения могут изменяться, и ваша программа перестанет работать.


Название: Re: Вызов private virtual метода
Отправлено: Bepec от Март 29, 2014, 14:49
Это если разные версии компиляторов и прочего. А в стабильной версии они стабильны. Хотя хз. 10 раз у меня получалось из 10. Мб на 100 и не получилось бы :)


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 29, 2014, 15:10
Разумеется если private/protected было объявлено - значит так надо и "снимать" его плохо/безыдейно. Но чисто технически я не вижу здесь никакого "хака", проблем со смещениями полей и.т.п. Класс "A" существует, класс "B" я объявляю сам. Где же "хак"?

Аналогии с "#define private public" неуместны, то всего лишь кромсание исходников (благо макруха это позволяет)


Название: Re: Вызов private virtual метода
Отправлено: LisandreL от Март 29, 2014, 15:37
нет, public/private - это реализация одного из фундаментальных принципов, инкапсуляции. Но из-за того, что С++ решил быть расширенным Си, всё оказалось довольно криво и обходимо
Не думаю, что в этом дело. Java вроде бы изначально планировалась как высокоуровневый ОЯП, что не спасает её от своего Public'а Морозова (http://habrahabr.ru/post/75661/).

Ну и вообще что тут может сделать компилятор, кроме как вызвать A::Print? Запретить ослабление доступа для виртуальных функций на этапе компиляции? Бросить исключение/упасть на этапе исполнения?

Igors, а как вы оцениваете плохость/безыдейность Qt:
Код
C++ (Qt)
class A : public QObject
{
   Q_OBJECT
public:
   void emitSignal()
   {
       emit sA();
   }
signals:
   void sA();
};
 
class B : public QObject
{
   Q_OBJECT
 
private slots:
   void sB()
   {
       qDebug( "B" );
   }
};
 
int main(int argc, char *argv[])
{
   A a;
   B b;
   QObject::connect( &a, SIGNAL( sA() ), &b, SLOT( sB() ) );
   a.emitSignal();
}
 


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 29, 2014, 18:21
Где же "хак"?
Хак в том, что вы пытаетесь вызвать метод, который вызывать вам запрещено.

Аналогии с "#define private public" неуместны, то всего лишь кромсание исходников (благо макруха это позволяет)
В чем кромсание? Добавили одну строку перед инклюдом и получили, тот же эффект, что и у вас, да еще и без создания дополнительных/не нужных классов.


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 30, 2014, 09:30
Ну и вообще что тут может сделать компилятор, кроме как вызвать A::Print? Запретить ослабление доступа для виртуальных функций на этапе компиляции? Бросить исключение/упасть на этапе исполнения?
"За ЧТО?"  :)

Igors, а как вы оцениваете плохость/безыдейность Qt:
Насколько я понял, приватный слот успешно коннектится. Не считаю это плохим/безыдейным, т.к. слоты могут активно использоваться для любых манипуляций, и не только ядром, но и приложением (напр PythonQt)


Название: Re: Вызов private virtual метода
Отправлено: Bepec от Март 30, 2014, 09:57
Хыхыхы... Похоже тут отсутствие информации сыграло свою роль. Igors - коннектятся слота самим объектом, которому принадлежит слот. А не "левым дядей".


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 30, 2014, 10:08
Хыхыхы... Похоже тут отсутствие информации сыграло свою роль. Igors - коннектятся слота самим объектом, которому принадлежит слот. А не "левым дядей".
??? Не понял, макрос SLOT вообще разворачивает в строку, какой дядя?


Название: Re: Вызов private virtual метода
Отправлено: LisandreL от Март 30, 2014, 10:52
Не понял, макрос SLOT вообще разворачивает в строку, какой дядя?
Ну это же вопрос реализации во что он разворачивается.
Я в данном коде для себя вижу нарушение «приватности» - мы адресуем приватный слот вне класса:
Цитировать
QObject::connect( &a, SIGNAL( sA() ), &b, SLOT( sB() ) );
то во что оно там под капотом Qt разворачивается дело 10-ое.
Логически аналогичный код с новой (Qt5) нотацией, например, вообще не соберётся:
Цитировать
QObject::connect( &a, &A::sA, &b, &B::sB );

Но тут вопрос скорее в идеологии Qt. Зачем слот вообще можно делать приватным?
Если просто что бы его нельзя было вызвать как функцию, то всё OK, а если что бы слот был доступен только внутри класса (а у меня такие потребности возникали), то это не работает в старой нотации.


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 30, 2014, 10:59
Я в данном коде для себя вижу нарушение «приватности» - мы адресуем приватный слот вне класса:
Цитировать
QObject::connect( &a, SIGNAL( sA() ), &b, SLOT( sB() ) );
Ну чего же "адресуем", когда "вызываем" - хотя ни то ни другое неверно, это просто строка
"1sB()", синтаксис валидный


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 30, 2014, 13:18
Ну чего же "адресуем", когда "вызываем" - хотя ни то ни другое неверно, это просто строка
"1sB()", синтаксис валидный
А причем здесь, строка это или число? Это детали реализации.
Смысл здесь в том, что можем вызвать метод, который нам вызывать запрещено. Короче, это брат вашего хака. :)


Название: Re: Вызов private virtual метода
Отправлено: _Bers от Март 31, 2014, 01:41
Добрый день

Право, растерялся - если "private" то "извне" никак не вызвать. Однако простой пример ниже вызывает (печатается "А")
Код
C++ (Qt)
#include <stdio.h>
 
class A {
private:
virtual void Print( void ) { printf("A\n"); }
};
 
class B : public A {
public:
B( A & a ) : mA(a) {}
 
void Print( void ) { printf("B\n"); }
 
void Test( void )
{
B & b = static_cast <B &> (mA);
b.Print();
}
 
// data
A & mA;
};
 
int main(int argc, char *argv[])
{
A a;
B b(a);
b.Test();
 
return 0;
}
 
Please "ткните носиком" в статейки где это обсуждается


Запуск функции-члена происходит от имени класса B, в котором она определяется, как public.



Название: Re: Вызов private virtual метода
Отправлено: _Bers от Март 31, 2014, 01:57
Короче - квалификатор наследования может урезать права доступа (к базовому классу) но никогда их не расширит.

Компилятору пофигу, чего и как там было в базе.
Когда он делает проверку модификатора доступа, то он проверяет исключительно доступность конкретного метода от имени конкретного класса.

Если наследнику доступна функция-член предка, он вообще может по собственному желанию изменить его модификатор в своём пространстве имени:

http://rextester.com/XJOSPM85703

Код:
#include <iostream>
using namespace std;

struct A
{
protected:
   void foo() { cout<<"A::foo\n"; }
};

struct B: A   //<--- наследник имеет доступ к защищенному методу
{
public:
   using A::foo;  //<--- класс B захотел, что бы метод стал стал public
};

int main()
{
    std::cout << "Hello, world!\n";
   
    B b;
    b.foo();
}

И если в классе B объявлен метод Print с модификатором доступа public, значит его можно запустить снаружи.

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

Метод Print объявленный в классе B - собственность класса B, и поэтому модификатор этого метода определяется классом B.


Название: Re: Вызов private virtual метода
Отправлено: Igors от Март 31, 2014, 09:18
struct B: A   //<--- наследник имеет доступ к защищенному методу
{
public:
   using A::foo;  //<--- класс B захотел, что бы метод стал стал public
};
К защищенному имеет, но не к приватному - using не пройдет если A::foo объявлено private. И часто затруднительно "иметь экземпляр B" только чтобы достучаться до A::foo


Название: Re: Вызов private virtual метода
Отправлено: Old от Март 31, 2014, 09:19
И часто затруднительно "иметь экземпляр B" только чтобы достучаться до A::foo
define же. :)


Название: Re: Вызов private virtual метода
Отправлено: _Bers от Март 31, 2014, 23:14
К защищенному имеет, но не к приватному - using не пройдет если A::foo объявлено private. И часто затруднительно "иметь экземпляр B" только чтобы достучаться до A::foo

Верно.

Но ничто не мешает создать в наследнике точно такую же функцию, которая собою скроет метод предка.
Что и произошло в вашем случае с виртуальной функцией-членом.

http://rextester.com/ROT67619

Код:
#include <iostream>
using namespace std;

struct base
{
    virtual void foo()const { cout << __FUNCTION__ <<endl; }
};

struct der:base
{
    virtual void foo()const { cout << __FUNCTION__ <<endl; }
};


int main()
{
    std::cout << "Hello, world!\n";
    
    der d;
    
    d.foo();
    d.base::foo(); //<--- так как, она не приватна в имени класса base, то мы можем вытащить её из под тени
}

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

Каждый класс отвечает только за свою собственность и не отвечает ни за каких своих наследников.
Поэтому, в пространстве имени наследника можно установить любые модификаторы для собственности этого наследника, а так же для любого члена, к которому этот наследник имеет доступ.