2009-07-05 4 views
5

Предположим, что Y - производный класс из класса X, а X объявляет foo виртуальным. Пусть y имеет тип (Y *). Тогда ((X *) y) -> foo() выполнит Y-версию foo(), но ((X) * y) .foo() выполнит версию X. Можете ли вы сказать мне, почему полиморфизм не применяется в случае разыменования? Я ожидаю, что любой синтаксис даст версию Y foo().C++ полиморфизм ((X *) y) -> foo() vs ((X) * y) .foo()

ответ

0

Я считаю, что это просто из-за того, как указывается язык. Ссылки и указатели используют, по возможности, позднее связывание, в то время как объекты используют раннее связывание. Можно было бы связать последнее связывание во всех случаях (я думаю), но компилятор, который сделал это, не будет следовать спецификациям C++.

8

Листинг всегда (*) создает новый объект типа, на который вы клонируете, который построен с использованием объекта, который вы выполняете.

Кастинг для X * создает новый указатель (то есть объект типа X *). Он имеет то же значение, что и y, поэтому он все еще указывает на тот же объект типа Y.

Кастинг для X создает новый X. Он построен с использованием *y, но в остальном он не имеет ничего общего со старым объектом , В вашем примере foo() вызывается на этот новый «временный» объект, а не на объект, на который указывает y.

Вы правы, что динамический полиморфизм применяется только к указателям и ссылкам, а не к объектам, и именно по этой причине: если у вас есть указатель на X, то то, на что он указывает, может быть подклассом X. Но если у вас есть X, то это X, и больше ничего. виртуальные вызовы были бы бессмысленными.

(*) если оптимизация не позволяет исключить код, который не изменит результат. Но оптимизация не позволяет изменить то, что вызывается функцией foo().

+1

... и ((X &) * y) .foo() _does_ вызывает производное foo, потому что приведение Y & к X и не создает новый X. – Doug

+0

Да, я должен, вероятно, сказать: «всегда создает новый объект типа, на который вы производите, если только тип, который вы выполняете, не является типом объекта». Кастинг для ссылочного типа «создает» новую ссылку, связанную с исходным объектом. –

0

Я думаю, что объяснение Дарта Эры является правильным, и вот почему я думаю, что C++ ведет себя таким образом:

кода (X) * у, как создать локальную переменную, которая имеет типа X. компилятор должен выделять пространство sizeof (X) в стеке и отбрасывать любые дополнительные данные, включенные в объект типа Y, поэтому при вызове foo() он должен выполнить версию X. Компилятору будет сложно вести себя таким образом, чтобы вы могли назвать версию Y.

Код (X *) y как создание указателя на объект, и компилятор знает, что объект, на который указывает объект, является X или подклассом X. Во время выполнения, когда вы разыскиваете указатель и вызываете foo с помощью "- > foo() "определяется класс объекта и используется правильная функция.

+0

Помните, что (X) * y определяется как X (* y), то есть вызов некоторого конструктора X для создания нового объекта X. Это было бы не просто сложно, даже не желательно, чтобы объект X имел Y-версию foo(), вызываемую на нем, только потому, что он был построен с использованием объекта Y. –

2

разыменования (*y части) в порядке, но бросок ((X) части) создает новый (временный) объект специально класса X - это то, что отливка означает. Таким образом, объект должен иметь виртуальную таблицу из класса X - подумайте, что в литье будут удалены все члены экземпляра, добавленные Y в подклассу (действительно, как может быть, может быть, о них может быть)? потенциально может быть катастрофой, если какой-либо из переопределений Y должен был выполнить - насколько они знают, что this указывает на экземпляр Y, в комплекте с добавленными членами и все ... когда это знание было ложным!

версия, в которой вы разыгрываете указатели, конечно, совершенно разные - *X имеет только одни и те же биты, как Y*, так что по-прежнему указывает на вполне допустимый экземпляр Y (на самом деле, это, указывая на y, конечно).

Печальный факт заключается в том, что для обеспечения безопасности копия ctor класса действительно должна быть вызвана, как аргумент, экземпляром этого класса - не любого подкласса; потеря добавленных членов экземпляра & c просто повреждает. Но единственный способ убедиться в том, что следует следовать отличному совету Haahr «Не подклассы конкретных классов» ... хотя он пишет о Java, совет, по крайней мере, хорош для C++ (у которого есть эта копия ctor) slicing "!)

+0

Если потеря дополнительных членов Y повреждается при построении копирования X, то либо класс Y не удовлетворяет принципу подписи Лискова, либо, скорее всего, вызывающий не понял, что он кастинг (непреднамеренный фрагмент), и считает, что он все еще имеет объект класса Y. В любом случае, я не думаю, что ошибка, как таковая, вызывает конструктор копирования с Y. Он не понимает, что результатом является X, построенный из Y, как и любой другой конструктор 1-arg , а не что-то полиморфное. –

+0

Непредвиденный фрагмент является наиболее вероятной причиной, и если вы не подклассы конкретных классов (в дополнение к другим причинам Haahr дает), это один вид несчастного случая, которого не произойдет! -) –

+0

Конечно, это правильное правило большой палец. Я также согласен с Хааром, есть случаи, когда стоит подцепить вашу руку и подклассифицировать, потому что иногда наследование действительно полезно. Но на самом деле можно было бы удовлетворить Лискова, в котором большинство подклассов конкретных классов ошибочно. Если, как говорит Хаар, * любой * аспект суперкласса «затаскивается непреднамеренно», то вы уже потерпели неудачу. Но вы можете подклассы конкретных классов, в редких случаях это имеет смысл. Если это так, копия ctor имеет смысл, и те, кто понимает, что нарезка, в любом случае избегают этого, не делая этого. –

10

Вы разрезаете часть объекта Y и копируете объект в объект X. Вызываемая функция вызывается на объект X, и, следовательно, вызывается функция X.

Когда вы укажете тип в C++ в объявлении или акте, это означает, что объявленный объект или casted-to фактически относится к этому типу, а не к производному типу.

Если вы хотите, чтобы просто рассматривать объект является быть типа X (то есть, если вы хотите статический тип выражения быть X, но все-таки хочу, чтобы обозначить Y объект), то вы применяете к ссылочный тип

((X&)*y).foo() 

Это будет вызывать функцию в Y объекта, а не резать и не копировать в X объекта. На этапах, это делает

  • разыменования указателя y, который имеет тип Y*. Выделение вызывает lvalue выражение типа Y. Выражение lvalue может фактически обозначать объект производного типа, даже если его статический тип является его базой.
  • Относится к X&, что является ссылкой на X. Это даст lvalue выражение типа X.
  • Вызовите функцию.

Ваш оригинальный бросок сделал

  • разыменования указателя y.
  • Полученное выражение, отлитое от X. Это приведет к операции копирования в новый объект X. В результате это выражение представляет собой выражение rvalue выражение статического типа X. Динамический тип обозначенного объекта: такжеX, как и все rvalue выражения.
  • Вызовите функцию.
Смежные вопросы