Question

Je souffre actuellement d'un pet cérébral. Je l'ai déjà fait auparavant, mais je ne me souviens pas de la syntaxe exacte ni du code que j'ai écrit car je travaillais pour une autre entreprise à l'époque. J'ai cet arrangement:

class P
{
// stuff
};

class PW : public P
{
// more stuff
};

class PR : public P
{
// more stuff
};

class C
{
public:
    P GetP() const { return p; }    
private:
    P p;
};

// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

J'aimerais maintenant que P soit interchangeable avec PW et PR (pour que PW et PR puissent être interchangés). Je pourrais probablement m'en tirer avec les conversions, mais ce changement de code a eu lieu plusieurs fois dans ce module seulement. Je suis à peu près sûr que c’est un opérateur, mais je ne me souviens plus de quoi que ce soit.

Comment puis-je rendre P interchangeable avec PW et PR avec un minimum de code?

Mettre à jour : pour clarifier un peu la situation. P représente Project et R et W, respectivement Reader et Writer. Tout ce que le Reader a, c'est le code de chargement - pas de variables, et le rédacteur a le code pour écrire simplement. Il doit être séparé car les sections Lecture et Écriture ont différentes classes et dialogues de gestionnaire, ce qui n’est pas une préoccupation majeure de Projects, à savoir la manipulation des fichiers de projet.

Mise à jour: Je dois également pouvoir appeler les méthodes de P et de PW. Donc, si P a une méthode a () et PW comme méthode, appelez b (), alors je pourrais:

PW p = c.GetP();
p.a();
p.b();

Il s'agit essentiellement de rendre la conversion transparente.

Était-ce utile?

La solution

Si vous souhaitez que cette partie soit compilée:



// ...
    P p = c.GetP( ); // valid
    PW p = c.GetP( ); // invalid
    PR p = c.GetP( ); // invalid
// ...

Vous devez être capable de construire / convertir un P en PW ou en PR. Vous devez faire quelque chose comme ça:



class PW : public P
{
    PW(const P &);
// more stuff
};

class PR : public P
{
    PR(const P &);
// more stuff
};

Ou voulez-vous dire quelque chose de plus semblable à:


class P
{
    operator PW() const;
    operator PR() const;
// stuff
};

Autres conseils

Vous essayez de contraindre les variables réelles plutôt que les pointeurs. Pour ce faire, il faudrait un casting. Cependant, si votre définition de classe ressemblait à ceci:

class C

    {
        public: 
            P* GetP() const { return p; }
        private:
            P* p;
    }

Ensuite, que p soit un pointeur sur un P, un PW ou un PR, votre fonction ne changerait pas et les fonctions (virtuelles) appelées sur le P * renvoyé par la fonction utiliseraient l'implémentation de P, PW ou PR en fonction de ce que le membre p était ..

Je pense que l'élément clé à retenir est le Principe de substitution de Liskov . Puisque PW et PR sont des sous-classes de P, ils peuvent être traités comme s'ils étaient Ps. Cependant, les PG ne peuvent pas être traités comme des PR, et inversement.

Dans le code ci-dessus, vous avez l'opposé du problème découpage en tranches .

Vous essayez d’affecter un P à un PW ou à un PR qui contient plus d’informations que l’objet source. Comment est-ce que tu fais ça? Supposons que P ne comporte que 3 variables de membre, mais que PW compte 12 membres supplémentaires. Où en sont les valeurs lorsque vous écrivez PW p = c.GetP()?

Si cette affectation est réellement valide , ce qui devrait en réalité indiquer une bizarrerie de conception, je mettrais en œuvre PW::operator=(const P&) et PR::operator=(const P&), PW::PW(const P&) et PR::PR(const P&). Mais je ne dormirais pas trop bien cette nuit-là.

Cela fait quelque chose de sensé, étant donné que tout est passé par valeur. Je ne sais pas si c'est ce à quoi vous pensiez.

class P
{
public:
    template <typename T>
    operator T() const
    {
        T t;
        static_cast<T&>(t) = *this;
        return t;
    }
};

Pour rendre PW et PR utilisables via un P, vous devez utiliser des références (ou des pointeurs). Vous devez donc vraiment changer l’interface de C pour qu’elle renvoie une référence.

Le principal problème de l'ancien code était que vous copiez un P dans un PW ou un PR. Cela ne fonctionnera pas car le PW et le PR ont potentiellement plus d'informations qu'un P et, du point de vue du type, un objet de type P n'est pas un PW ou un PR. Bien que PW et PR soient tous deux P.

Changez le code en ceci et cela compilera: Si vous souhaitez renvoyer différents objets dérivés d'une classe P au moment de l'exécution, la classe C doit potentiellement pouvoir stocker tous les différents types que vous attendez et être spécialisée au moment de l'exécution. Ainsi, dans la classe ci-dessous, je vous autorise à vous spécialiser en passant un pointeur sur un objet qui sera renvoyé par référence. Pour être sûr que l'objet est protégé contre les exceptions, j'ai placé le pointeur dans un pointeur intelligent.

class C
{
    public:
        C(std::auto_ptr<P> x):
            p(x)
        {
            if (p.get() == NULL) {throw BadInit;}
        }
        // Return a reference.
        P& GetP() const { return *p; }        
    private:
        // I use auto_ptr just as an example
        // there are many different valid ways to do this.
        // Once the object is correctly initialized p is always valid.
        std::auto_ptr<P> p;
};

// ...
P&  p = c.GetP( );                   // valid
PW& p = dynamic_cast<PW>(c.GetP( )); // valid  Throws exception if not PW
PR& p = dynamic_cast<PR>(c.GetP( )); // valid  Thorws exception if not PR
// ...

Vous voulez peut-être parler de l'opérateur dynamic_cast ?

Ils ne sont pas totalement interchangeables. PW est un P. PR est un P. Mais P n'est pas nécessairement un PW et ce n'est pas nécessairement un PR. Vous pouvez utiliser static_cast pour convertir les pointeurs de PW * à P * ou de PR * à P *. Vous ne devriez pas utiliser static_cast pour convertir les objets réels dans leur super-classe à cause de & "Slicing &"; Par exemple. Si vous lancez un objet de PW à P, le contenu supplémentaire dans PW sera & "en tranches &"; de. Vous ne pouvez pas non plus utiliser static_cast pour convertir de P * en PW *. Si vous devez vraiment le faire, utilisez dynamic_cast, qui vérifiera au moment de l'exécution si l'objet appartient réellement à la bonne sous-classe, et vous donnera une erreur d'exécution si ce n'est pas le cas.

Je ne suis pas sûr de ce que vous voulez dire exactement, mais supportez-moi.

Ils sont déjà en quelque sorte. Il suffit d'appeler tout ce qui est P et vous pouvez prétendre que PR et PW sont des P. PR et PW sont toujours différents.

L'utilisation des trois équivalents entraînerait des problèmes avec le principe de Liskov . Mais alors pourquoi leur donner des noms différents s’ils sont vraiment équivalents?

Les deuxième et troisième seraient invalides car il s’agit d’un upcast implicite, ce qui est dangereux en C ++. Cela est dû au fait que la classe vers laquelle vous lancez a plus de fonctionnalités que la classe à laquelle elle est assignée. Par conséquent, à moins que vous ne le convertissiez explicitement vous-même, le compilateur C ++ émettra une erreur (du moins ce devrait être le cas). Bien sûr, cela simplifie un peu les choses (vous pouvez utiliser RTTI pour certaines choses qui peuvent concerner ce que vous voulez faire en toute sécurité sans faire appel à la colère du mauvais objet) - mais la simplicité est toujours un bon moyen d’aborder les problèmes.

Bien sûr, comme indiqué dans quelques autres solutions, vous pouvez contourner ce problème - mais je pense qu'avant d'essayer de le contourner, vous voudrez peut-être repenser la conception.

Utilisez une référence ou un pointeur sur P plutôt qu'un objet:

class C
{
 public:
  P* GetP() const { return p; }
 private:
  P* p;
};

Ceci permettra à un PW * ou à un PR * d'être lié à C.p. Cependant, si vous devez passer d'un P à un PW ou à un PR, vous devez utiliser dynamic_cast & Lt; PW * & Gt; (p), qui renverra la version PW * de p ou NULL de p n'est pas un PW * (par exemple, parce que c'est un PR *). Dynamic_cast a toutefois une surcharge, et il vaut mieux l'éviter si possible (utilisez des virtuels).

Vous pouvez également utiliser l'opérateur typeid () pour déterminer le type d'exécution d'un objet, mais il présente plusieurs problèmes, notamment celui que vous devez inclure et le fait qu'il ne détecte pas de dérivation supplémentaire.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top