Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Начинающим";
Текущий архив: 2011.02.27;
Скачать: [xml.tar.bz2];

Вниз

Операции с классами   Найти похожие ветки 

 
Nickolay   (2010-12-08 09:27) [0]

Здравствуйте!
 Кто может объяснить следующую особенность операции as (Obj as TObj)?
В справке говорится, что она возвращает ссылку на объект Obj как на
объект уже класса TObj. Причём Obj должен быть из TObj или быть его потомком.
Короче, as приводит Obj к нужному типу TObj.
 Я завожу два класса - предок TA и потомок TB. Переобъявляю в TB поле,
перекрываю статический метод. Затем преобразую тип объекта B из TB к TA.
Но тип не преобразовывается с помощью as! Функциональное преобразование
работает. Если правым операндом поставить переменную-ссылку на класс TA,
то и as работает. А вот если правый операнд - просто тип TA, то не работает!
Что это - ошибка в компиляторе?

type
  TAClass = class of TA;
  TA = class  // объявление класса-предка
  public
     Val: Integer;
     procedure Met;
  end;
  TB = class(TA)  // объявление класса-потомка
  public
     Val: string;  // теперь поле предка скрыто
     procedure Met;
  end;

procedure TA.Met;
begin
  WriteLn("Это TA.Met")
end;

procedure TB.Met;
begin
  WriteLn("Это TB.Met")
end;

var
  B: TB;
  Aref: TAClass;

begin
  Aref := TA;          // переменная-ссылка на класс-предок
  B := TB.Create;  // B - экземпляр класса-потомка
  B.Val := "Ноябрь"; // работаем со своим полем
  B.Met;               // вызываем свой статический метод
  TA(B).Val := 18; // работаем с полем предка
  TA(B).Met;           // вызываем метод предка
  (B as Aref).Val := 18;// работаем с полем предка
  (B as Aref).Met;      // вызываем метод предка
  (B as TA).Val := 18;  // здесь операция as не работает - ошибка
  (B as TA).Met;        // здесь операция as не работает - вызов метода потомка


 
RWolf ©   (2010-12-08 09:34) [1]

Открываем справку:

> object as class
> … object must be an instance of the class denoted by class
> or one of its descendants, or be nil


 
Palladin ©   (2010-12-08 09:51) [2]

(B as TA).Val := 18;  // здесь операция as не работает - ошибка
 (B as TA).Met;        // здесь операция as не работает - вызов метода потомка

не имеют смысла


 
Nickolay   (2010-12-08 10:40) [3]


> RWolf ©   (08.12.10 09:34) [1]
> Открываем справку:

object as class
object must be an instance of the class denoted by class or one of its descendants, or be nil
Объект object должен быть экземпляром класса, обозначенного правым операндом class, или одним из его потомков, или константой nil.

У меня B - потомок TA.


 
Nickolay   (2010-12-08 10:44) [4]


> Palladin ©   (08.12.10 09:51) [2]

Смысл имеют: во-первых, это определение операции as,
во-вторых, в C++ такие перекрытия полей тоже возможны и
для такого случая там тоже есть спец операция ::,
а в Delphi для типа класс постарались взять многие возможности
из C++.


 
И. Павел ©   (2010-12-08 10:48) [5]

> спец операция ::,

В паскале используйте self. для доступа к элементам предка.

И еще для переопределения в паскале есть спец. слово (посмотрите описание стандартных классов, и вы его увидите). Савсем недавно на форуме обсуждали: зачем его добавили. Вот видимо чтобы сишников на первых порах мучать.


 
Ega23 ©   (2010-12-08 10:55) [6]


> В паскале используйте self. для доступа к элементам предка.

Это вредный совет. Неправильный.


 
RWolf ©   (2010-12-08 11:03) [7]


>  [3] B - потомок TA.

да, моя ошибка.
в принципе, похоже на баг.
var c:ta;
 (B as TA).Met;        // вызывает TB.Met
 c:=b as ta;
 c.met; //вызывает TA.Met


 
И. Павел ©   (2010-12-08 11:05) [8]

> Это вредный совет. Неправильный.

А это - необоснованное возражение.
Поясните.


 
Ega23 ©   (2010-12-08 11:19) [9]


> Поясните.


Self - это переменная. Указатель, указывающий либо на данный экземпляр класса, либо на сам класс в случае классовых методов.
Для обращения к методам предка используется директива inherited


 
Nickolay   (2010-12-08 11:20) [10]


> И. Павел ©   (08.12.10 10:48) [5]
>
> В паскале используйте self. для доступа к элементам предка.
>

Строго говоря self для доступа к текущему объекту класса. Это удобно для
списка динамических объектов. Операция же as специально для преобразования типа.


 
И. Павел ©   (2010-12-08 11:20) [11]

> Self - это переменная. Указатель, указывающий либо на данный
> экземпляр класса, либо на сам класс в случае классовых методов.
> Для обращения к методам предка используется директива inherited

Точно. Я спутался. inherited, конечно.


 
Servy ©   (2010-12-08 11:30) [12]

> Nickolay

Весьма забавное наблюдение, я при некотором, пусть далеко не всеобъемлющем опыте программирования на Делфи на такой вариант использования as не натыкался. Спасибо, что поделились.

На самом деле, все более менее логично, хотя в справке явно не описано. Если компилятор может проверить на стадии компиляции, что B принадлежит типу TA, то он просто выкидывает as. Легко убедиться в этом, например так:

type
 TA = class
 end;

 TB = class(TA)
 private
   FVal: Integer;
 public
   property Val: Integer read FVal write FVal;
 end;

var
 B: TB;
 A: TA;

begin
 B := TB.Create;

 (B as TA).Val := 2;


Данный код успешно компилируется, хотя в типе TA вообще нет никакого Val.  Если изменить тип переменной B на TObject, то компилироваться данный код перестанет. Так как все поля предка принадлежат и потомку, то никаких проблем в стандартных ситуациях не возникает.

А вот если вы используете приведение к неизвестному на этапе компиляции классу (в вашем примере Aref), то от as компилятору не избавиться и он выдает ожидаемый вами результат.

Да, ну и очевидно, что давать одинаковые имена полям предка и потомка - плохая идея, которая приводит к плохой читаемости. Посему, видимо, на эти грабли почти никто и не наступал. Если первая реинкарнация Val и вторая - логически одна и та же переменная, то можете использовать тип Variant, способный хранить как число, так и строку или другой тип данных. Если же это две различных переменных с различными значениями, дайте им разные имена, чтобы надпись A.Val не могла быть понята превратно (ни компилятором, ни человеком).


 
Nickolay   (2010-12-08 11:47) [13]


> Servy ©   (08.12.10 11:30) [12]
1) Если компилятор может проверить на стадии компиляции, что B принадлежит типу TA, то он просто выкидывает as.

2) Данный код успешно компилируется, хотя в типе TA вообще нет никакого Val.

3) Да, ну и очевидно, что давать одинаковые имена полям предка и потомка - плохая идея
 

1)   Для проверки принадлежности классу есть другая операция - is

2)  Он успешно компилируется по той же причине, что и с полем:
 Val - это свойство потомка TB

3)  Посмотрите справку в Дельфи по "Fields". Там как раз приводится пример по переобъявлению полей, который завершается фразой:
"Both fields, however, exist in the TDescendant object; the inherited Value is hidden by the new one, and can be accessed through a typecast".

А вот "typecast", похоже, можно сделать только с помощью функционального преобразования.


 
И. Павел ©   (2010-12-08 12:01) [14]

Если подвести курсор мыши к Val в строке (B as TA).Val то подсказка сообщает: TB.Val
Чертовщина.
А в новых версиях Delphi это не поправили?


 
Servy ©   (2010-12-08 12:09) [15]


> 1)   Для проверки принадлежности классу есть другая операция
> - is


Что не мешает компилятору работать так, как он работает, а мне - комментировать его работу так, как я ее понимаю. По-моему, ваше заявление не относится к сути вопроса. Возможно, вы не поняли мое сообщение или я его непонятно написал, попробую уточнить.

as - checked typecast. Проверяемое приведение типов. Если у нас имеется:

B: TB;

и констуркция

(B as TA)

, причем TA - предок TB, то проверка всегда вернет пройдет (всегда вернет true). Потому что в переменной B не должно лежать ничего, несовместимого с классом TB. А класс TB совместим с классом TA, ибо потомок - наследует все его свойства. Таким образом, в данном контексте, когда проверка возвращает всегда true, typecast не производится. Если бы B был объявлен базового типа TObject, то тут еще бабка надвое сказала, совместим ли он с TA. Посему, если в вашем коде из [0] изменить объявление переменной B:

var
 B: TObject;


То ваш код будет таки работать (правда, часть где вы явно обращаетесь к B.Val придется по понятным причинам закомментировать). Таким образом:


> А вот "typecast", похоже, можно сделать только с помощью
> функционального преобразования.


неверно, as тоже вполне себе делает typecast. Она лишь не делает его лишь в случае, когда проверка совместимости типов совершенно не нужна. В большинстве ситуаций, когда нет скрытия видимости полей или методов потомком, такое поведение ничем не отличается от ожидаемого.


> 2)  Он успешно компилируется по той же причине, что и с
> полем:
> Val - это свойство потомка TB


Да, никакой разницы между полем и свойством в данном контексте нет. Свойство написал по привычке, никакого скрытого смысла не подразумевалось. Демонстрация состояла в том, что мы обращаемся к свойству (полю), которого у объекта TA в принципе нет, а значит выражение имеет тип TB. Почему оно имеет такой тип - я предположил выше.


> Посмотрите справку в Дельфи по "Fields"


Fields are statically bound; that is, references to them are fixed at compile time. To see what this means, consider the following code:

Там кусок кода приведен для демонстрации механизмов работы Делфи, а не в качестве "делайте вот так". То, что делать вот так - не стоит (по крайней мере не имея весьма веских причин, которых не приводилось) для меня очевидно. Думаю, другие обитатели форума найдут более весомые аргументы, вступать в полемику по сему поводу считаю малополезным занятием.


 
Servy ©   (2010-12-08 12:33) [16]

Попробую еще уточнить. as обычно используется так:


TA = class
end;

TB = class(TA)
public
 procedure Smth;
end;

var
 A: TA;

begin
 Randomize;
 if Random(2) = 0 then
   A := TA.Create
 else
   A := TB.Create;

 // нам нужно получить доступ к методу TB;
 // если рандом создал нам экземпляр TA - словим Exception - на то и проверяемой приведение типов
 (A as TB).Smth;


Вы же используете его наоборот:

TA = class
public
 procedure SmthElse;
end;

TB = class(TA)
public
 procedure Smth;
end;

var
 B: TB;

begin
 Randomize;
 
 // так мы сделать не может, ибо в переменную типа TB идут только классы - наследники TB
 //if Random(2) = 0 then
 //  B := TA.Create
 //else
 //  B := TB.Create;
 
 B := TB.Create;
 
 // приводим тип к TA и вызываем метод TA
 (B as TA).SmthElse;
 
 // однако B - и так наследник TA и имеет нужный метод, так что танцы с бубном не нужны, можно писать просто
 // B.SmthElse;
 // что собственно и делает компилятор.


В вашем же примере Smth и SmthElse имеют одно и то же имя, что приводит к обозначенной вами проблеме. Давать одинаковые имена полям или методам - плохая идея в целом, так как человек идентифицирует по имени сущность. И если вместо одной сущности Val у объекта окажется две сущности с таким именем, и где-то идет работа с одной, а где-то с другой, а мне бы пришлось потом поддерживать этот код, я бы на того, кто это написал сильно обиделся.

Если вы все еще считаете, что это баг - создайте тикет в QC. Я не вижу в наблюдаемом поведении проблемы.


 
Nickolay   (2010-12-08 12:52) [17]


> Servy ©   (08.12.10 12:09) [15]
1) Там кусок кода приведен для демонстрации механизмов работы Делфи, а не в качестве "делайте вот так".

2) Если бы B был объявлен базового типа TObject, то тут еще бабка надвое сказала, совместим ли он с TA. Посему, если в вашем коде из [0] изменить объявление переменной B:

var
B: TObject;

1) А я никогда не утверждал и не утверждаю, что "надо делать так".
 Я просто показываю, что данный "механизм Дельфи", похоже, работает не всегда.

 2) Зачем TObject? Я просто поменяю ролями TA и TB, только предка надо будет инициализировать как потомка:

var
  A: TA;
begin
  A := TB.Create;         // A - экземпляр всё-таки класса-потомка
  TB(A).Val := "Ноябрь"; // работаем с полем потомка
  TB(A).Met;                // вызываем метод потомка
  (A as TB).Val := "Ноябрь"; // работаем с полем потомка
  (A as TB).Met;                // вызываем метод потомка

Здесь as работает...


 
Медвежонок ХМЛ ©   (2010-12-08 12:58) [18]

Здесь as работает...

многое зависит от типа вызываемого метода.
если метод статический, то то, чей именно метод будет вызван - определяется типом переменной.
А если метод динамический, то типом объекта, на который содержится ссылка в переменной. Сам тип переменной при этом роли не играет.


 
Nickolay   (2010-12-08 13:03) [19]


> Servy ©   (08.12.10 12:33) [16]
1) Давать одинаковые имена полям или методам - плохая идея в целом, так как человек идентифицирует по имени сущность. И если вместо одной сущности Val у объекта окажется две сущности с таким именем, и где-то идет работа с одной, а где-то с другой, а мне бы пришлось потом поддерживать этот код, я бы на того, кто это написал сильно обиделся.

2) Если вы все еще считаете, что это баг - создайте тикет в QC.


1) Тогда можете смело обижаться на Страуструпа. Он в в своём описании C++ прямо-таки смакует возможность переобъявления одноимённых полей классов и доступа к скрытому полю с помощью операции ::.
Что касается меня, то я отнюдь не "за" такие штучки, а тоже "против". Но раз они придумали такие механизмы в C++ и Delphi, то пусть отвечают за их работоспособность, а не вводят в заблуждение.

2) Объясните, пожалуйста, подробнее, что значит "тикет в QC".


 
Servy ©   (2010-12-08 13:11) [20]


> А я никогда не утверждал и не утверждаю, что "надо делать
> так".
>  Я просто показываю, что данный "механизм Дельфи", похоже,
>  работает не всегда.


Ага, а я вас поблагодарил за интересную демонстрацию особенности. Мне вполне понятно, как работает данный механизм, и в каком случае результат может отличаться от ожидаемого. И эти случаи относятся к варианту, который, как мы с вами согласились, в реальных проектах лучше не использовать.


> Зачем TObject? Я просто поменяю ролями TA и TB


TObject - предок TA, так что большой разницы нет. Главное, что в этом случае as не бесполезен, а значит компилятор его не выбросит.


 
Nickolay   (2010-12-08 13:12) [21]


> Медвежонок ХМЛ ©   (08.12.10 12:58) [18]
А если метод динамический, то типом объекта, на который содержится ссылка в переменной.


Это естественно! И для этого методы объявляются виртуальными или динамическими, а потом перекрываются, чтобы реализовать принцип полиморфизма. Поэтому если во всех этих примерах метод виртуализировать, а потом перекрывать, то для потомка будет вызываться метод потомка.


 
Медвежонок ХМЛ ©   (2010-12-08 13:21) [22]

то для потомка будет вызываться метод потомка.

Ну осталось еще уяснить, что потомок это или не потомок - определяется не типом переменной а типом экземпляра.


 
Nickolay   (2010-12-08 13:47) [23]


> Медвежонок ХМЛ ©   (08.12.10 13:21) [22]


Я говорю про то же, но только другими словами.
type
  TAClass = class of TA;
  TA = class  // объявление класса-предка
     procedure Met; virtual;
  end;
  TB = class(TA)  // объявление класса-потомка
     procedure Met; override;
  end;

procedure TA.Met;
begin
  WriteLn("Это TA.Met")
end;

procedure TB.Met;
begin
  WriteLn("Это TB.Met")
end;

var
  A: TA;
  B: TB;
  Aref: TAClass;
begin
  A := TB.Create;             // A - экземпляр всё-таки класса-потомка
  TB(A).Met;
  (A as TB).Met;
  Aref := TA;                   // переменная-ссылка на класс-предок
  B := TB.Create;             // B - экземпляр класса-потомка
  B.Met;
  TA(B).Met;
  (B as Aref).Met;
  (B as TA).Met;

Во всех этих случаях будет вызываться метод потомка.


 
Медвежонок ХМЛ ©   (2010-12-08 13:51) [24]

но не указан случай :
A.Met;

когда
A := TB.Create;


 
Nickolay   (2010-12-08 13:58) [25]


> Медвежонок ХМЛ ©   (08.12.10 13:51) [24]
> но не указан случай :
> A.Met;

Согласен, это не указал.

A.Met;
опять-таки вызовет метод потомка и опять-таки в силу полиморфизма,
то есть, другими словами, в A сидит ссылка на TB и поэтому вызывается его метод.
 Таким образом, повторяю, что мы говорим про одно и то же, но только разными словами.


 
Palladin ©   (2010-12-08 14:31) [26]


> Nickolay   (08.12.10 10:44) [4]

Ты приводишь объект к типу класса, не к другому классу, а к его метатипу, если можно так сказать. Применяешь на уровне времени-исполнения. Зачем?


 
Nickolay   (2010-12-08 14:46) [27]


> Palladin ©   (08.12.10 14:31) [26]
Зачем?


Изучаю язык Дельфи.


 
Palladin ©   (2010-12-08 14:52) [28]

Ну понятно. Я тоже могу долбится головой об стену, мотивировав это изучением сопромата.


 
Nickolay   (2010-12-08 15:03) [29]


> Palladin ©   (08.12.10 14:52) [28]


Это всё понятно. По теме чего-нибудь скажете? Если - нет, то - проехали.


 
DiamondShark ©   (2010-12-08 15:18) [30]

Пиплы, травы отсыпьте.


 
Ega23 ©   (2010-12-08 15:24) [31]


> Пиплы, травы отсыпьте.


Тссс, не спугни такую прелесть.


 
Nickolay   (2010-12-09 08:25) [32]


> Servy ©   (08.12.10 12:33) [16]

Я извиняюсь. Я на этот Ваш предпоследний пост тоже написал, может Вы не заметили? Меня всё-таки интересует, что значит "тикет в QC". Не подскажете?
Кстати, все свои примеры я прогнал через компилятор Free Pascal в режиме {$Mode Delphi} и всё прошло без всяких "багов". С помощью as получал доступ из потомка к скрытому полю и статическому методу предка во всех случаях.


 
И. Павел ©   (2010-12-09 08:40) [33]

> тикет в QC

QC -Embarcadero Quality Central
тикет - сообщение им
http://qc.embarcadero.com/wc/qcmain.aspx?p=10


> Кстати, все свои примеры я прогнал через компилятор Free
> Pascal

А в новых версиях Delphi кто-нибудь пробовал?


 
Servy ©   (2010-12-09 10:23) [34]


> 1) Тогда можете смело обижаться на Страуструпа. Он в в своём
> описании C++ прямо-таки смакует возможность переобъявления
> одноимённых полей классов и доступа к скрытому полю с помощью
> операции ::.
> Что касается меня, то я отнюдь не "за" такие штучки, а тоже
> "против". Но раз они придумали такие механизмы в C++ и Delphi,
>  то пусть отвечают за их работоспособность, а не вводят
> в заблуждение.


У меня и так большие претензии к этому изобретателю, так что дополнительная обида за смакование штучек сильно мое мнение о нем не изменит.

По поводу штучек - даже с буквоедской позиции (так как с точки зрения здравого смысла понятно, что такой typecast нафиг не сдался) можно вам возразить:

the inherited Value is hidden by the new one, and can be accessed through a typecast

Доступ к скрытому значению может быть получен с помощью typecast.

as - описывается как "checked typecast", а не просто "typecast", про него в приведенном выше фрагменте не говорилось. Таким образом, можно считать, что as производит typecast, но может как давать, так и не давать доступа к скрытым полям. Если вам нужен именно доступ к скрытым полям - используйте именно typecast.

Впрочем, заниматься буквоедством - занятие весьма унылое, я больше не буду ^_^.


> А в новых версиях Delphi кто-нибудь пробовал?

Пробовал в 2010, та же ерунда.


 
Ega23 ©   (2010-12-09 14:35) [35]

В QC не забудьте написать...


 
Nickolay   (2010-12-10 10:55) [36]


> И. Павел ©   (09.12.10 08:40) [33]
QC -Embarcadero Quality Central
тикет - сообщение им
http://qc.embarcadero.com/wc/qcmain.aspx?p=10

Ega23 ©   (09.12.10 14:35) [35]
В QC не забудьте написать...

Я ещё раз извиняюсь. Зашёл по указанному адресу и ничего не понял, в силу, видимо, тупости. Единственно, что похоже на возможность связаться с ними это "connect with us...". Зарегистрировался, например, в Facebook. А там чего только нет - и фотографии, и друзья, и безопасность и т.д., но вот как через них послать своё сообщение в это QC - опять убейте, не пойму. На "стену" им объявление написать что ли?

Просветите тупого человека, пожалуйста!


 
Ega23 ©   (2010-12-10 11:46) [37]

1. А почему вы решили, что это "глюк Delphi"?
2. Насчёт QC - заходи под тем ником или e-mail, под которым Delphi регистрировал (или активизировал? Не, регистрировал) у них на сайте.


 
Anatoly Podgoretsky ©   (2010-12-10 15:16) [38]

> Ega23  (10.12.2010 11:46:37)  [37]

Или не заходи совсем.



Страницы: 1 вся ветка

Форум: "Начинающим";
Текущий архив: 2011.02.27;
Скачать: [xml.tar.bz2];

Наверх




Память: 0.58 MB
Время: 0.005 c
15-1289748125
Sergeant88
2010-11-14 18:22
2011.02.27
Помогите найти компонент


15-1289831257
R_e_T_r_O
2010-11-15 17:27
2011.02.27
Записать из делфи в эксель


15-1289204232
savva
2010-11-08 11:17
2011.02.27
[Работа] требуется Delphi разработчик (Москва)


2-1291707614
novichek
2010-12-07 10:40
2011.02.27
определить ОС


15-1290093745
БарЛог
2010-11-18 18:22
2011.02.27
Программа для создания меню запуска





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский