È una cattiva pratica usare l'ereditarietà per associare metodi con un contenitore di base?

3

Fondamentalmente, ho un codice che assomiglia a questo. È un typedef e un insieme di metodi che ruotano attorno a quel typedef .

class foo {
    // Foo stuff...

    // Registration Stuff
    private:
        typedef std::map<std::string, std::string> MTRegistrationParams;

        void DispatchRegistrationMessage (MTRegistrationParams const &, std::string destination_id);
        static std::string BuildRegistrationMessage (MTRegistrationParams const &, std::string message_id); // Only called by DispatchRegistrationMessage

    // more Foo stuff...
};

Penso che potrebbe essere più chiaro fare solo una piccola mini-classe, per aiutare a separare logicamente questi metodi dalla struttura di Foo.

class foo {
    // Foo stuff...

    // Registration Stuff
    private:
        struct MTRegistrationParams : public std::map<std::string, std::string> {
            void DispatchRegistrationmessage (foo &, std::string const & destination_id);
            private:
                std::string BuildRegistrationMessage (std::string const & message_id);
    };

    // more Foo stuff...
};

Il fatto è che ... di solito non creo classi derivate a meno che non si tratti di ereditare metodi e derivare da un contenitore standard sembra DAVVERO strano.

Sono eccessivamente preoccupato o deriva da contenitori standard solo per decorarli con metodi veramente cattivi?

    
posta QuestionC 12.09.2014 - 01:15
fonte

3 risposte

3

Derivando da un contenitore standard C ++ dovrebbe sembrare davvero strano, perché non sono mai stati progettati per essere usati come classe base.

La più grande fonte di problemi è se il tuo contenitore 'esteso' ha bisogno di sovrascrivere il comportamento del contenitore sottostante (incluso il distruttore!), o se il tuo contenitore 'esteso' ha vincoli aggiuntivi che non esistono per il contenitore standard sottostante .
Quando si passa un contenitore "esteso" come un puntatore / riferimento a una funzione che si aspetta un contenitore standard, tale funzione può inavvertitamente interrompere gli invarianti del contenitore "esteso" chiamando le funzioni sbagliate della classe base. E se ti capita di distruggere un contenitore 'esteso' attraverso un puntatore alla sua classe base, ti attieni a un comportamento indefinito.

Nell'esempio di questa domanda, le probabilità di un simile comportamento scorretto sono prossime a zero, ma se inizi a utilizzare questo modello, è facile atterrare in situazioni in cui fa importa. Per questo motivo, è strongmente consigliato non derivare dai contenitori standard.

Inoltre, il metodo DispatchRegistrationMessage non è molto più facile da usare, perché ora devi passare un oggetto Foo ed è molto scomodo utilizzare operatori sovraccaricati (come operator[] di std::map ) su this .

    
risposta data 12.09.2014 - 08:28
fonte
3

Am I being overly concerned, or is deriving from standard containers just to decorate them with methods genuinely bad practice?

No, hai ragione a essere preoccupato. I contenitori std non sono stati pensati per essere ereditati, solo incapsulati (e l'ereditarietà da essi dovrebbe essere UB).

Penso che la tua soluzione finale dovrebbe essere su queste linee:

class MTRegistrationParams {
    std::map<std::string, std::string> data; // encapsulated
public:
    void DispatchRegistrationmessage (std::string const & destination_id);

    decltype(data.begin()) begin() { return data.begin(); }
    decltype(data.end()) end() { return data.end(); }
    decltype(data.size()) size() { return data.size(); }
    // forward ano other map methods you need, here
private:
    std::string BuildRegistrationMessage (std::string const & message_id);
};

class foo {
    // Foo stuff...

    // Registration Stuff
private:
    typedef MTRegistrationParams params;

    // more Foo stuff...
};
    
risposta data 12.09.2014 - 10:14
fonte
-1

va bene. Perché la classe foo non è associata in modo logico con MTRegistrationParams. è meglio fornire una classe interiore. Quindi se l'utente vuole usare foo solo allora non è necessario esporre le funzionalità di MTRegistrationParams. come l'iteratore per la lista.

    
risposta data 12.09.2014 - 08:29
fonte

Leggi altre domande sui tag