2010-11-28 24 views
13

Je viens de trouver les paragraphes suivants dans le projet de norme C++ 03 concernant la conversion de pointeur en membre.Conversion de pointeur en membre

4,11/2 Pointeur sur les conversions membres

Un rvalue de type « pointeur vers un membre de B de type cv T », où B est un type de classe, peut être converti en un rvalue de type "Pointeur sur un membre de D de type cv T", où D est une classe dérivée (clause 10) de B. Si B est une classe de base inaccessible (article 11), ambiguë (10.2) ou virtuelle (10.1) de D, un programme qui nécessite cette conversion est mal formé. Le résultat de la conversion fait référence au même membre que le pointeur vers le membre avant la conversion, mais il fait référence au membre de la classe de base comme s'il s'agissait d'un membre de la classe dérivée . Le résultat se réfère au membre dans l'instance de D de B. Puisque le résultat a un type "pointeur vers le membre de D de type cv T", il peut être déréférencé avec un objet D. Le résultat est le même que si le pointeur à un membre de B ont été déréférencé avec le sous-objet B de D. La valeur du pointeur d'élément nul est converti en la valeur du pointeur d'élément nul de la type.52 de destination)

5.2.9/9 static_cast

un rvalue de type « pointeur vers un membre de D de type CV1 T » peut être converti en un rvalue de type « pointeur vers un membre de B de type CV2 T », où B est une classe de base (clause 10) de D, si une conversion standard valide de "pointeur en membre de B de type T" en "pointeur en membre de D de type T" existe (4.11), et cv2 est le même cv -qualification en tant que, ou une plus grande cv-qualification que, cv1.63) La valeur de pointeur de membre null (4.11) est convertie en la valeur de pointeur de membre null du type de destination. Si la classe B contient le membre d'origine ou est une base ou une classe dérivée de la classe contenant le membre d'origine, le pointeur vers le membre d'origine pointe vers le membre d'origine. Sinon, le résultat de la distribution est indéfini. [Note: bien que la classe B ait besoin de ne contienne pas le membre original, le type dynamique de l'objet sur lequel le pointeur vers le membre est déréférencé doit contenir le membre original; voir 5.5. ]

Alors, voici ma question. Comme le dit le 5.2.9/9, un pointeur vers un membre de D peut être converti en un pointeur vers un membre de B, s'il existe une conversion valide décrite en 4.11/2. Est-ce que cela signifie que s'il y a un membre 'm' de D qui n'est pas hérité de B, le pointeur sur le membre 'm' ne peut pas être converti en type de pointeur en membre de B?

class Base { }; 
class Derived : public Base 
{ 
    int a; 
}; 
typedef int Base::* BaseMemPtr; 
BaseMemPtr pa = static_cast<BaseMemPtr>(&Derived::a); // invalid, as per 5.2.9/9 ? 

Dans la note 5.2.9/9, il est dit aussi que, bien que ne doit pas contenir la classe B du membre d'origine, le type dynamique de l'objet sur lequel le pointeur de membre est déréférencé doit contenir le membre d'origine .

Je suis confus avec le libellé du paragraphe. Le code ci-dessus est-il valide?

J'ai recherché le site, et il y a une question similaire, c++ inheritance and member function pointers, dont la réponse ne couvrait que le cas où la conversion de pointeur en membre de classe de base en pointeur en membre de classe dérivée.

+0

Vous affectez un pointeur à la valeur du membre de données à un pointeur sur la variable de fonction membre. Sinon, oui, "le résultat de la distribution est indéfini". – Potatoswatter

+0

Merci, je l'ai réparé. – ashen

Répondre

10

Le code que vous avez écrit est parfaitement valide. Il n'y a rien de mal à cela (à part le fait que Derived::a est privé). Il est bien formé et le comportement est défini (jusqu'à présent). Comme le dit la partie citée de la norme, il est parfaitement légal d'améliorer les pointeurs de membres en utilisant un static_cast explicite, ce qui est exactement ce que vous faites. 5.2.9/9 ne dit jamais que le membre pointé doit être présent dans la classe de base.

En outre, comme vous avez cité correctement de la norme, la présence de l'organe réel dans l'objet est nécessaire plus tard au moment de déréférencer du pointeur, pas au moment de l'initialisation. Ceci, bien sûr, dépend du type dynamique de l'objet utilisé sur le côté gauche de l'opérateur d'accès de membre (->* ou .*). Le type n'est connu qu'à l'exécution et ne peut donc pas être vérifié par le compilateur.

Cette exigence est comprise comme une simple note en 5.2.9/9, mais il est repris dans une forme plus formelle 5,5/4

4 Si le type dynamique de l'objet ne contient pas le membre auquel le pointeur se réfère, le comportement est indéfini.

Ainsi, par exemple, dans le contexte de votre exemple, les lignes de code ci-dessous sont bien formés

Base b; 
b.*pa; // 1 

Derived d; 
d.*pa; // 2 

Base *pb = &d; 
pb->*pa; // 3 

Cependant, le premier déréférencer produit un comportement non défini (puisque l'objet b ne contient pas le membre), tandis que le second et le troisième sont parfaitement légaux.

+0

Excellente réponse. Mais je ne peux pas résister à mentionner que 'sizeof b. * Pa' donne un comportement défini, malgré le" déréférencement "d'un membre inexistant. (Parce que rien en C++ n'est simple ... :-P) –

+1

@j_random_hacker: Eh bien, oui. Dans mon cas, l'occurrence d'un comportement indéfini est liée au processus de déréférencement du pointeur lors de l'évaluation de l'expression, alors que dans le cas de sizeof, l'argument n'est jamais évalué. – AnT

+0

VC émet un avertissement C4407 quand je static_cast un pointeur sur un membre de la classe dérivée vers le second type de base, donc selon la norme et votre réponse, ce n'est pas conforme, n'est-ce pas? – ashen