web-dev-qa-db-fra.com

Comment fonctionnent les opérateurs de conversion en C++?

Considérons cet exemple simple:

template <class Type>
class smartref {
public:
    smartref() : data(new Type) { }
    operator Type&(){ return *data; }
private:
    Type* data;
};

class person {
public:
    void think() { std::cout << "I am thinking"; }
};

int main() {
    smartref<person> p;
    p.think(); // why does not the compiler try substituting Type&?
}

Comment fonctionnent les opérateurs de conversion en C++? (i.e) quand le compilateur essaie-t-il de substituer le type défini après l'opérateur de conversion?

30
AraK

Certaines situations aléatoires où des fonctions de conversion sont utilisées et non utilisées suivent. 

Tout d'abord, notez que les fonctions de conversion ne sont jamais utilisées pour convertir le même type de classe ou un type de classe de base.

Conversion lors du passage d'argument

La conversion lors du passage d'argument utilisera les règles d'initialisation de la copie. Ces règles ne prennent en compte que les fonctions de conversion, qu’elles soient converties en référence ou non. 

struct B { };
struct A {
  operator B() { return B(); }
};
void f(B);
int main() { f(A()); } // called!

La transmission d'arguments n'est qu'un des contextes d'initialisation de la copie. Une autre est la forme "pure" utilisant la syntaxe d'initialisation de copie

B b = A(); // called!

Conversion en référence

Dans l'opérateur conditionnel, la conversion en un type de référence est possible, si le type converti en est une lvalue. 

struct B { };
struct A {
  operator B&() { static B b; return b; }
};

int main() { B b; 0 ? b : A(); } // called!

Une autre conversion en référence est lorsque vous liez une référence, directement

struct B { };
struct A { 
  operator B&() { static B b; return b; }
};

B &b = A(); // called!

Conversion en pointeurs de fonction

Vous pouvez avoir une fonction de conversion en un pointeur ou une référence de fonction et, lorsqu'un appel est passé, elle peut être utilisée. 

typedef void (*fPtr)(int);

void foo(int a);
struct test {
  operator fPtr() { return foo; }
};

int main() {
  test t; t(10); // called!
}

Cette chose peut devenir utile parfois. 

Conversion en types non-classe

Les conversions implicites qui se produisent toujours et partout peuvent également utiliser des conversions définies par l'utilisateur. Vous pouvez définir une fonction de conversion qui renvoie une valeur booléenne

struct test {
  operator bool() { return true; }
};

int main() {
  test t;
  if(t) { ... }
}

(Dans ce cas, la conversion en bool peut être sécurisée par le safe-bool idiom , pour interdire les conversions vers d'autres types entiers.) Les conversions sont déclenchées partout où un opérateur intégré attend un certain type. Les conversions peuvent toutefois être gênantes. 

struct test {
  void operator[](unsigned int) { }
  operator char *() { static char c; return &c; }
};

int main() {
  test t; t[0]; // ambiguous
}

// (t).operator[] (unsigned int) : member
// operator[](T *, std::ptrdiff_t) : built-in

L'appel peut être ambigu, car pour le membre, le second paramètre nécessite une conversion et pour l'opérateur intégré, le premier nécessite une conversion définie par l'utilisateur. Les deux autres paramètres correspondent parfaitement, respectivement. L'appel peut être non ambigu dans certains cas (ptrdiff_t doit alors être différent de int). 

Modèle de fonction de conversion

Les modèles permettent quelques jolies choses, mais il vaut mieux être très prudent à leur sujet. Ce qui suit rend un type convertible en n'importe quel type de pointeur (les pointeurs de membres ne sont pas vus comme des "types de pointeurs"). 

struct test {
  template<typename T>
  operator T*() { return 0; }
};

void *pv = test();
bool *pb = test();
46

Le "." L'opérateur n'est pas surchargeable en C++. Et chaque fois que vous dites x.y, aucune conversion ne sera automatiquement effectuée sur x.

16
anon

Les conversions ne sont pas magiques. Ce n'est pas parce que A a une conversion en B et B a une méthode foo que a.foo () appellera B :: foo ().

Le compilateur essaie d'utiliser une conversion dans quatre situations

  1. Vous convertissez explicitement une variable en un autre type
  2. Vous transmettez la variable en tant qu'argument à une fonction qui attend un type différent à cette position (les opérateurs sont considérés comme des fonctions ici).
  3. Vous affectez la variable à une variable d'un type différent
  4. Vous utilisez la variable copy-construct ou initialisez une variable d'un type différent 

Il existe trois types de conversions, autres que ceux liés à l'héritage

  1. Conversions intégrées (par exemple, Int-à-Double)
  2. Construction implicite, où la classe B définit un constructeur prenant un seul argument de type A et ne le marque pas avec le mot clé "explicit"
  3. Opérateurs de conversion définis par l'utilisateur, où la classe A définit un opérateur B (comme dans votre exemple)

Comment le compilateur décide-t-il quel type de conversion utiliser et quand (surtout lorsqu'il y a plusieurs choix) est assez complexe, et je ferais un mauvais travail en essayant de le condenser en une réponse à SO. La section 12.3 de le standard C++ traite de la construction implicite et des opérateurs de conversion définis par l'utilisateur.

(Il y a peut-être des situations ou des méthodes de conversion auxquelles je n'ai pas pensé, alors merci de les commenter ou de les éditer si quelque chose manque.)

8
Tyler McHenry

La conversion implicite (que ce soit par des opérateurs de conversion ou par des constructeurs non explicites) se produit lors de la transmission de paramètres à des fonctions (y compris les opérateurs surchargés et par défaut pour les classes). En plus de cela, certaines conversions implicites sont effectuées sur des types arithmétiques (ainsi, l'ajout d'un caractère et d'un long entraîne l'ajout de deux longs, avec un résultat long).

La conversion implicite ne s'applique pas à l'objet sur lequel un appel de fonction membre est effectué: aux fins de la conversion implicite, "this" n'est pas un paramètre de fonction.

6
Steve Jessop

Tu devrais faire 

((person)p).think();

Le compilateur ne dispose pas des informations nécessaires à la conversion automatique en personne, vous avez donc besoin d'une diffusion explicite.

Si vous voulez utiliser quelque chose comme

person pers = p;

Ensuite, le compilateur a des informations pour la conversion implicite à personne.

Vous pouvez avoir "casting" à travers les constructeurs:

class A
{
public:
   A( int );
};


A a = 10; // Looks like a cast from int to A

Voici quelques exemples brefs. Le casting (implicite, explicite, etc.) nécessite plus d'explications. Vous pouvez trouver des détails dans des livres C++ sérieux (voir les questions sur les livres C++ sur le dépassement de capacité de la pile pour obtenir de bons titres, comme celui-ci ).

0
Cătălin Pitiș

// Table virtuelle Fuction (VFT)  

#include <iostream>

using namespace std;

class smartref {
public:
virtual char think() { }//for Late bindig make virtual function if not make virtual function of char think() {} then become early binding and pointer call this class function 
    smartref() : data(new char) { }
  operator char(){ return *data; }
private:
    char* data;
};

class person:public smartref
{
public:
    char think() { std::cout << "I am thinking"; }
};

int main() {
    smartref *p;//make pointer of class
    person o1;//make object of class
    p=&o1;//store object address in pointer
    p->think(); // Late Binding in class person
return 0;
}
0
MAB

Le compilateur tentera une conversion (!) Définie par l'utilisateur (opérateur implicite ou opérateur) si vous essayez d'utiliser un objet (référence) de type TU est requis. 

Cependant, l'opérateur . essaiera toujours d'accéder à un membre de l'objet (référence) sur son côté gauche. C'est juste la façon dont c'est défini. Si vous voulez quelque chose de plus sophistiqué, c'est pour cela que operator->() peut être surchargé. 

0
sbi