Fortschrittsanzeige
-
Aufruf1 und Aufruf2 machen doch immer das gleiche, oder nicht?
Nö. Ganz und garnicht.
B::Sfunc( (B*)&a);
Hier castest du erst A* nach B*, bevor du aber über den Zeiger auf das referenzierte Objekt zugreifst castest du wieder zurück.
Du dereferenzierst also *niemals* einen Zeiger vom Typ B. Sondern immer nur einen vom Typ A.((B*)&a)->func();
Hier hingegen *dereferenzierst* du einen Zeiger vom Typ B, obwohl ein Objekt vom Typ A referenziert wird. Der operator -> macht den Unterschied. Und dieser ist gewaltig.
Sicher, die meisten Compiler wandeln eine Methode B::Func() in eine Funktion
ähnlich Func_B(B* this) um. Und falls es sich nicht um eine virtuelle Methode handelt wird der Aufruf ((B*)&a)->func(); bei solchen Compilern auch
zu Func_B((B*)&a);Das muss aber nicht so sein. Fakt ist, dass der op-> laut C++ Standard zu einer Dereferenzierung des Zeigers führt. Egal ob das eine Implementation dann auch wirklich tut. Und diese Dereferenzierung ist es, die IMO zu undefiniertem Verhalten führt.
Das ist auch der Grund warum sowas undefiniertes Verhalten hat:
struct A { void Func() {cout << "Hallo" << endl;} }; int main() { ((A*)0)->Func(); }
Das wird bei den meisten Compiler funktionieren, da der Nullzeiger nicht dereferenziert wird. Es ist aber trotzdem undefiniert, da laut Standard hier ein Nullzeiger dereferenziert wird.
-
Gehen wir auf Assemberebene, um zu verdeutlichen, was ich meine!
class A { int i; }; class B : public A { void func() { i=1; } }; A a1, a2; ((B*)&a1)->func(); ((B*)&a2)->func();
// Asm
lea ecx, [a1]
call 0x25242322
lea ecx, [a2]
call 0x25242322Was macht nun die Funktion func():
mov eax, dword ptr [this]
mov [eax], 1Wir sehen also folgendes:
Für eine Funktion aus B existiert ein realer Function call, ob nun eine Objekt dafür instanziert wurde oder nicht bleibt egal!
Die Funktion in B schiebt die zugewiesene 1 auf den this-Pointer, was uns zeigt, dass this der Adresse des ersten Datenelements des Objekts entspricht.
->
Der function call wird immer funktionieren, solange auf einen Datenbereich zugegriffen wird, der vom Basisobjekt auch zur Verfügung gestellt wird.Selbst wenn die Funktion virtuell ist, klappt der Aufruf, da das Schlüsselwort virtual nur sagt, dass der Funktionszeiger in der Basisklasse durch den der abgeleiteten Klasse zu ersetzen ist.
-
Hallo,
hier nochmal ein paar wichtige Stellen aus dem C++ Standard:((B*)&a)->func();
Member-Access über den ->:
5.2.5/1:A postfix expression followed by a dot . or an arrow ->,
optionally followed by the keyword template (14.8.1), and
then followed by an id-expression, is a postfix expression.
The postfix expression before the dot or arrow
is evaluated, the result of that evaluation, together
with the id-expression, determine the result
of the entire postfix expression.Und Fußnote 58:
The evaluation happens even if the result is unnecessary to
determine the value of the entire postfix expression,
for example if the id-expression denotes a static
member.Die Fußnote macht klar, warum mein letztes Beispiel mit dem Nullpointer undefiniertes Verhalten hat.
5.2.5/2:
[...] For the second option (arrow) the type of
the first expression (the pointer expression)
shall be "pointer to class" (of a complete type).
In these cases, the id-expression shall name a member
of the class or of one of its base classes[...]
Soweit so gut. ((B*)&a) ist vom "pointer to class" (of a complete type) (hier B). Und func ist ein Name eines Member der Klasse B.5.2.5/3
If E1 has type "pointer to class X", then the expression
E1->E2 is converted to the equivalent form
(*(E1)).E2; the remainder of 5.2.5 will address only
first option (dot).Dazu Fußnote 59:
Note that if E1 has type "pointer to class X",
then (*(E1)) is an lvalue.Jetzt wird's wieder wichtig:
5.2.5/4:[...]
Otherwise, if E1.E2 refers to a non-static member function, and the type
of E2 is "function of (parameter type list) cv returning T", then E1.E2
is not an lvalue. The expression designates a non-static member function.
The expression can be used only as the left-hand operand of a member
function call (9.3).[...]Also flux nach 9.3 geschaut:
9.3.1/1:
A nonstatic member function may be called for an object of its
class type, or for an object of a class derived (clause 10) from
its class type, using the clas member access syntax.
[...]
If a nonstatic member function of a class X is called for
an object that is not of type X, or of a type
derived from X, the behavior is undefined.Der letzte Satz bestätigt meine Aussage. Func ist eine nicht-statische Methode der Klasse B. Das Objekt für das wir diese Methode aufrufen ist aber vom Typ A. A ist nicht von B abgeleitet, also ist das Verhalten undefiniert.
Hier ist es also nicht der cast der Bösewicht sondern der anschließende member-access.
-
@RenéG
Du scheint mich nicht zu verstehen. Mir geht es darum was der Ausdruck für Standard-C++ bedeutet. Und da ist er *undefiniert*. Es kann also alles passieren. Vom Formatieren deiner Festplatte bis zum Programmabsturz. Von läuft toll bis läuft nur an Dienstagen.Es hilft da nichts, wenn du mit Assembler oder sonstigen *Implementationsdetails* die von Compiler zu Compiler variieren kommst. C++ ist nicht auf Assemblerebene definiert!
Ich bezweifle nicht, dass das ganze auf dem VC funktioniert. Ich sage nur, dass es kein legales Standard-C++ ist.
Selbst wenn die Funktion virtuell ist, klappt der Aufruf, da das Schlüsselwort virtual nur sagt, dass der Funktionszeiger in der Basisklasse durch den der abgeleiteten Klasse zu ersetzen ist.
Nö. Das ist wieder eine Implementationssicht. Es steht nirgends im Standard geschrieben, dass virtuelle Methoden über eine VMT mit Funktionszeigern und einem vptr implementiert sein müssen.
-
@Hume...
Womit wir ganz vom eigentlichen Thema abgewichen sind.
Mir ging es eigentlich von Anfang an darum, warum der Cast von GetDlgItem mit der MFC und Visual C++ funktioniert. Denn schliesslich sind wir ja hier nicht im Standard-C++-Forum.
Leider hab ich mich auch selber auf diese Diskussion eingelassen
-
Jaja, jetzt ausweichen...wenn man eine Diskussion anfängt, führt man sie auch zu Ende.
-
Womit wir ganz vom eigentlichen Thema abgewichen sind.
Da dies meine Schuld ist, muss ich mich dafür entschuldigen.
Ich habe von dem eigentlichen Thema überhaupt keine Ahnung (muss gestehen, dass ich den Thread nicht mal wirklich gelesen habe) sondern bin ja nur über den Email-Verkehr mit Knuddlbaer darauf gekommen.Insofern: Sorry für jedwedige Irritation die durch meine Beiträge ausgelöst wurde
Jaja, jetzt ausweichen...wenn man eine Diskussion anfängt, führt man sie auch zu Ende
Die Diskussion ist am Ende.
Die Standard-Sicht wurde von mir vertreten und spielt hier im *VC-Forum* nur eine untergeordnete Rolle. Die VC-Sicht wurde von René vertreten, sie ist für das *VC-Forum* entscheidend.Aus Standard-Sicht kann nach undefiniert nichts mehr folgen. Und aus VC-Sicht funktioniert alles.
Über was willst du also noch diskutieren?
-
Ok soweit thx, ich habe das nun verstanden, das wenn die Routinen so implementiert sind das sie auf keine Member oder auf kein this zugreifen einfach gecastet werden können (und zwar nur auf MFC + VC)
Daraus resultiert sich aber nun die Frage:
Ist es denn garantiert das die MFCControls das eben nicht tun ? bzw. wie bekomme
ich das raus ?Und die Frage, warum die Laufzeitinformation verloren geht wenn es denn das CWnd aus der HandleMap ist ist noch offen. Das hab ich noch nicht verstanden.
-
Aus AFXCMN.h:
///////////////////////////////////////////////////////////////////////////// // CProgressCtrl class CProgressCtrl : public CWnd { DECLARE_DYNAMIC(CProgressCtrl) // Constructors public: CProgressCtrl(); BOOL Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID); // Attributes void SetRange(short nLower, short nUpper); void SetRange32(int nLower, int nUpper); void GetRange(int& nLower, int& nUpper); int GetPos(); int SetPos(int nPos); int OffsetPos(int nPos); int SetStep(int nStep); // Operations int StepIt(); // Implementation public: virtual ~CProgressCtrl(); };
-> keine eigenen Membervariablen, mit Ausnahme von DECLARE_DYNAMIC, aber die sind eh static und damit nicht von belang
Aus WINCTRL2.CPP:
///////////////////////////////////////////////////////////////////////////// // CProgressCtrl BOOL CProgressCtrl::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) { // initialize common controls VERIFY(AfxDeferRegisterClass(AFX_WNDCOMMCTL_PROGRESS_REG)); CWnd* pWnd = this; return pWnd->Create(PROGRESS_CLASS, NULL, dwStyle, rect, pParentWnd, nID); } CProgressCtrl::~CProgressCtrl() { DestroyWindow(); }
Aus AFXCMN.INL:
_AFXCMN_INLINE CProgressCtrl::CProgressCtrl() { } _AFXCMN_INLINE void CProgressCtrl::SetRange(short nLower, short nUpper) { ASSERT(::IsWindow(m_hWnd)); ::SendMessage(m_hWnd, PBM_SETRANGE, 0, MAKELPARAM(nLower, nUpper)); } _AFXCMN_INLINE void CProgressCtrl::SetRange32(int nLower, int nUpper) { ASSERT(::IsWindow(m_hWnd)); ::SendMessage(m_hWnd, PBM_SETRANGE32, (WPARAM) nLower, (LPARAM) nUpper); } _AFXCMN_INLINE int CProgressCtrl::GetPos() { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_GETPOS, 0, 0); } _AFXCMN_INLINE int CProgressCtrl::OffsetPos(int nPos) { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_DELTAPOS, nPos, 0L); } _AFXCMN_INLINE int CProgressCtrl::SetStep(int nStep) { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_SETSTEP, nStep, 0L); } _AFXCMN_INLINE int CProgressCtrl::StepIt() { ASSERT(::IsWindow(m_hWnd)); return (int) ::SendMessage(m_hWnd, PBM_STEPIT, 0, 0L); }
-> Wenn Du z.B. weißt ein CWnd* pWnd ist ein ProgressCtrl, dann geht folgendes definitiv:
CWnd* pWnd = ... CProgressCtrl* pCtrl = (CProgressCtrl*)pWnd;
Nach Fragen?
-
Jepp
Das ich den cast machen kann hab ich ja gesehen. Rene und Hume haben sich ja ausführlich darüber unterhalten.
Was ich aber nicht verstehe ist warum der dynamic_cast nicht geht.
Denn das CWnd das ich von GetDlgItem bekomme ist doch das CWnd das im MFC Control steckt ?!
-
Tja, hab mal kurz in die MSDN geschaut, scheint wohl extra so gewollt zu sein, dass das mit dynamic_cast nicht geht. Da ich aber auch nicht wirklich erkennen kann, wozu dynamic_cast wirklich gut sein soll, bzw. ob an dem Ausdruck
A* pA = dynamic_cast<A*>(pB);
irgendetwas besser ist, als bei
A* pA = (A*)pB;
wüsste ich nicht warum ich ersteren verwenden sollte. Zumal die zweite Form deutlich kürzer ist.
-
Tja, hab mal kurz in die MSDN geschaut, scheint wohl extra so gewollt zu sein, dass das mit dynamic_cast nicht geht
Steht das irgendwo in der MSDN? Wenn ja, wo?
-
Geh in den Index und gib "dynamic_cast" ein.
-
dynamic_cast -> definiertes verhalten
lässt sich auch besser lesen als c Style casts
Ich denke ich werd mich da selbst mal durchackern müssen.
Wenn ich das genau weiß poste ich das mal, ich arbeite zu nahe am C++
Standard als das wir auf einen Nenner kommen könnenEventuell weiß Rene noch die Antwort darauf, wieso die laufzeit nicht vorhanden ist.... Schaut nach größerer Quelltextsucherei aus .
thx @ll
-
warum funktioniert dynamic_cast denn nicht. ist der zeiger dannach NULL oder wird eine bad_cast Exception geworfen?
-
Da ich aber auch nicht wirklich erkennen kann, wozu dynamic_cast wirklich gut sein soll, bzw. ob an dem Ausdruck
A* pA = dynamic_cast<A*>(pB);
irgendetwas besser ist, als beiA* pA = (A*)pB;
wüsste ich nicht warum ich ersteren verwenden sollte. Zumal die zweite Form deutlich kürzer istOha. Das du die Eigenschaften und Vorteile von dynamic_cast nicht kennst ist aber schade (und für mich auch etwas errschreckend).
dynamic_cast ist der *einzige* geprüfte Laufzeitcast in C++. dynamic_cast ist der *einzige* cast in C++ der ein sicheres Navigieren in komplexen Klassen-Hierarchien erlaubt (also Hierarchien mit mehrfacher und virtueller Vererbung).
Prinzipiell wird der dynamic_cast benutzt um Basisklassen-Referenzen/Zeiger in Referenzen (Zeiger) auf eine abgeleitete Klasse zu casten (down-cast). Um den umgekehrten Weg zu gehen (up-cast) oder um zwischen Referenzen/Zeigern zweier benachbarten abgeleiteter Klassen hin un her zu casten (sibling-cast)
Außerdem kann jeder Pointer nach void* gecastet werden.
Ein solcher Cast liefert einen void-Zeiger auf das am weitesten abgeleitete Objekt der Hierarchy.Der dynamic_cast kann dabei nur auf Pointer/Referenzen die Objekte von polymorphen Klassen verweisen angewendet werden. Als Teil des C++ RTTI Systems
prüft der cast zur Laufzeit die Gültigkeit des Ausdrucks.
Wird z.B. eine Basisklassenreferenz in eine Referenz vom Typ abgeleitete Klasse gecastet, obwohl das referenzierte Objekt nicht von diesem Typ (oder spezieller) ist, so wird eine bad_cast-Exception geworfen.
Für Pointer wird der Nullpointer geliefert. Das ist ein sehr wichtiger Unterschied zum C-Cast. Dieser bietet keine Möglichkeit einen ungültigen Cast zu erkennen.Du solltest dir am Besten generell noch mal die vier neuen C++ Cast-Operatoren anschauen. Diese bieten erhebliche Vorteile gegenüber dem alten one-size-fits-all C-Cast (auch als Vorschlaghammer bekannt). Jeder einzelne C++ Cast hat sein wohldefiniertes Einsatzgebiet. Und nur diese Casts halten sich z.B. auch an die in C++ definierten Access-Level.
Der einzige Nachteil der C++ Casts im Gegensatz zum C-Cast ist die Länge. Ich sehe das aber nicht als Nachteil. Erstens lassen sie sich dadurch besser finden und zweitens hast du in der Zeit in der du den Cast-Operator eintippst nochmal die Zeit darüber nachzudenken, ob ein Cast wirklich der richtige Weg ist.
Einen kleinen Einführungsartikel zum Thema C++ Casts findest du hier: http://www.acm.org/crossroads/xrds3-1/ovp3-1.html
-
Also ich find's weder schade noch erschreckend. Es gibt sicherlich Anwendungen wo dynamic_cast etc. notwendig/ sinnvoll sind. Aber ebenso gibt es genug Anwendungen wo die "billige" Version ebenso sinnvoll ist. Wenn ich definitiv weiß, dass ein per CWnd* pWnd gelieferter Zeiger eigentlich vom Typ CMyView* ist, dann brauche ich das nicht noch während der Laufzeit zu überprüfen. Und ich hab bisher nie, während irgendeiner Programmentwicklung, beim debuggen feststellen müssen, das irgendein "gecasteter" Pointer gar nicht das passende Objekt ist.
-
Wenn ich definitiv weiß, dass ein per CWnd* pWnd gelieferter Zeiger eigentlich vom Typ CMyView* ist
Aber CWnd::GetDlgItem liefert einen CWnd-Zeiger zurück und keinen z.B. CMyView*. Der CMyView Konstruktor würde nie aufgerufen und wenn CMyView noch irgendwelche zusätzlichen Variablen hat, werden diese nicht vernünftig initialisiert.
-
Die Diskusion haben wir weiter oben schon durch
Bei dem GetDlgItem sehe ich zwei möglichkeiten:
1. es gibt ein CWnd das ein HWND zugewiesen bekommt. Somit war das CWnd NIE ein MFC Control. Das der Aufruf für Methoden ohne zugriff auf this oder membervariablen funktioniert hat Rene für den VC weiter oben gezeigt. Dem C++ Standard nach gibt das undefiniertes verhalten. (s.o.)
2. Das CWnd was zurück gegeben wird aus der HandleMap ist das CWnd was in der Vererbungslinie des MFCControls steht. Und da sollte dann der dynamic_cast funktionieren. Ich sehe den Grund nicht warum die RTTI versagen sollte oder fehlen sollte.
-
Die MFC wurde vielleicht nicht mit RTTI Unterstützung kompiliert?