Domanda

C'è qualche motivo per rendere le autorizzazioni su una funzione virtuale C ++ ignorata diversa dalla classe base? C'è qualche pericolo nel farlo?

Ad esempio:

class base {
    public:
        virtual int foo(double) = 0;
}

class child : public base {
    private:
        virtual int foo(double);
}

La Faq C ++ afferma che è una cattiva idea , ma non dice perché.

Ho visto questo idioma in qualche codice e credo che l'autore stesse tentando di rendere definitiva la classe, basandosi sul presupposto che non è possibile sovrascrivere una funzione di membro privato. Tuttavia, Questo articolo mostra un esempio di funzioni private prioritarie. Naturalmente un'altra parte della faq C ++ raccomanda di non farlo.

Le mie domande concrete:

  1. C'è qualche problema tecnico con l'utilizzo di un'autorizzazione diversa per i metodi virtuali nelle classi derivate rispetto alla classe base?

  2. C'è qualche motivo legittimo per farlo?

È stato utile?

Soluzione

Il problema è che i metodi della classe Base sono il suo modo di dichiarare la sua interfaccia. In sostanza sta dicendo & Quot; Queste sono le cose che puoi fare agli oggetti di questa classe. & Quot;

Quando in una classe Derivata fai qualcosa che la Base aveva dichiarato pubblico privato, stai portando via qualcosa. Ora, anche se un oggetto derivato & Quot; is-a & Quot; Oggetto di base, qualcosa che dovresti essere in grado di fare con un oggetto di classe Base che non puoi fare con un oggetto di classe Derivata, rompendo il Principio della sostituzione di Liskov

Questo causerà un " tecnico " problema nel tuo programma? Forse no. Ma probabilmente significherà che l'oggetto delle tue classi non si comporterà in un modo che i tuoi utenti si aspettano che si comportino.

Se ti trovi nella situazione in cui questo è ciò che desideri (tranne nel caso di un metodo deprecato a cui fa riferimento in un'altra risposta), è probabile che tu abbia un modello di ereditarietà in cui l'ereditarietà non è realmente modellante " , is-a, quot &; (ad esempio Square di esempio di Scott Myers che eredita da Rectangle, ma non puoi modificare la larghezza di un Square indipendentemente dalla sua altezza come puoi fare per un rettangolo) e potresti dover riconsiderare le tue relazioni di classe.

Altri suggerimenti

Ottieni il risultato sorprendente che se hai un figlio, non puoi chiamare foo, ma puoi lanciarlo su una base e quindi chiamare foo.

child *c = new child();
c->foo; // compile error (can't access private member)
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child

Suppongo che potresti essere in grado di escogitare un esempio in cui non vuoi che una funzione sia esposta, tranne quando la stai trattando come un'istanza della classe base. Ma il fatto stesso che si presenti questa situazione suggerirebbe un cattivo design OO da qualche parte lungo la linea che dovrebbe probabilmente essere riformulato.

Non ci sono problemi tecnici, ma ti ritroverai con una situazione in cui le funzioni pubblicamente disponibili dipenderanno dal fatto che tu abbia un puntatore di base o derivato.

Questo secondo me sarebbe strano e confuso.

Può essere molto utile se si utilizza l'ereditarietà privata, ovvero si desidera riutilizzare una funzionalità (personalizzata) di una classe base, ma non l'interfaccia.

Può essere fatto e molto occasionalmente porterà a benefici. Ad esempio, nella nostra base di codice, stiamo usando una libreria che contiene una classe con una funzione pubblica che eravamo soliti usare, ma ora scoraggiamo l'uso a causa di altri potenziali problemi (ci sono metodi più sicuri da chiamare). Ci capita anche di avere una classe derivata da quella classe che gran parte del nostro codice utilizza direttamente. Quindi, abbiamo reso privata la data funzione nella classe derivata per aiutare tutti a ricordare di non usarla se possono aiutarla. Non elimina la possibilità di usarlo, ma catturerà alcuni usi quando il codice tenta di compilare, piuttosto che successivamente nelle revisioni del codice.

  1. Nessun problema tecnico, se si intende per tecnico in quanto vi è un costo di runtime nascosto.
  2. Se erediti la base pubblicamente, non dovresti farlo. Se erediti tramite protetto o privato, questo può aiutare a prevenire l'uso di metodi che non hanno senso a meno che tu non abbia un puntatore di base.

Un buon caso d'uso per l'eredità privata è un'interfaccia di eventi Listener / Observer.

Codice di esempio per l'oggetto privato:

class AnimatableListener {
  public:
    virtual void Animate(float delta_time);
};

class BouncyBall : public AnimatableListener {
  public:
    void TossUp() {}
  private:
    void Animate(float delta_time) override { }
};

Alcuni utenti dell'oggetto vogliono la funzionalità padre e altri vogliono la funzionalità figlio:

class AnimationList {
   public:
     void AnimateAll() {
       for (auto & animatable : animatables) {
         // Uses the parent functionality.
         animatable->Animate();
       }
     }
   private:
     vector<AnimatableListener*> animatables;
};

class Seal {
  public:
    void Dance() {
      // Uses unique functionality.
      ball->TossUp();
    }
  private:
    BouncyBall* ball;
};

In questo modo AnimationList può contenere un riferimento ai genitori e utilizza la funzionalità padre. Mentre Seal contiene un riferimento al figlio e utilizza la funzionalità figlio unica e ignora quella del genitore. In questo esempio, Animate non deve chiamare <=>. Ora, come menzionato sopra, <=> può essere chiamato eseguendo il casting sull'oggetto base, ma è più difficile e generalmente non dovrebbe essere fatto.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top