web-dev-qa-db-fra.com

Comment propager un ami pour les classes dérivées

Je veux avoir une hiérarchie de classes et pouvoir en créer des objets uniquement à l'intérieur d'une Factory.

Exemple:

class Base
{
    protected:
        Base(){};
        virtual void Init(){};

    friend class Factory;
};

class SomeClass : public Base
{
    public://I want protected here! Now it's possible to call new SomeClass from anywhere!
        SomeClass(){};
        void Init(){};
};

class Factory
{
    public:
        template<class T>
        T* Get()
        {
            T* obj = new T();
            obj->Init();

            return obj;
        }
};

int main()
{
    Factory factory;
    SomeClass *obj = factory.Get<SomeClass>();
}

Mon problème est que je veux pouvoir créer des objets uniquement à partir de Factory, mais je ne veux pas déclarer friend class Factory dans toutes les classes dérivées de Base. 

Est-il possible de propager un ami dans les classes dérivées? Existe-t-il un autre moyen d'obtenir ce comportement? 

23
Felics

Non, c'est délibérément impossible. 

Est un problème par encapsulation. 

Supposons que la classe "PswClass" gère tous les mots de passe, c’est-à-dire un ami en cascade avec une autre classe: si j’hérite de PswClass:

 class Myclass : public PswClass {
     .......
 }

De cette façon, je peux peut-être avoir accès à un champ qui serait privé. 

11
Luca Davanzo

L'amitié n'est ni héritée ni transitive, comme décrit ici: classe d'amis avec héritage

Après quelques expériences et une utilisation de ce hack Comment configurer un conteneur global (C++ 03)? , Je pense avoir trouvé un moyen de donner à la "fabrique" des droits uniques pour créer les objets. 

Voici un code rapide et sale. (Faites défiler vers le bas pour voir le hack.)

class Object {};

class Factory {
public:
    // factory is a singleton
    // make the constructor, copy constructor and assignment operator private.
    static Factory* Instance() {
        static Factory instance;
        return &instance;
    }

    public: typedef Object* (*CreateObjectCallback)(); 
    private: typedef std::map<int, CreateObjectCallback> CallbackMap; 

public: 
    // Derived classes should use this to register their "create" methods.
    // returns false if registration fails
    bool RegisterObject(int Id, CreateObjectCallback CreateFn) {
        return callbacks_.insert(CallbackMap::value_type(Id, createFn)).second; 
    }

    // as name suggests, creates object of the given Id type
    Object* CreateObject(int Id) {
        CallbackMap::const_iterator i = callbacks_.find(Id); 
        if (i == callbacks_.end()) { 
            throw std::exception(); 
        } 
        // Invoke the creation function 
        return (i->second)(); 
    }

    private: CallbackMap callbacks_; 
};

class Foo : public Object {
    private: Foo() { cout << "foo" << endl; }
    private: static Object* CreateFoo() { return new Foo(); }


public:
    static void RegisterFoo() {
        Factory::Instance()->RegisterObject(0, Foo::CreateFoo);     
    }
};

class Bar : public Object {
    private: Bar() { cout << "bar" << endl; }
    private: static Object* CreateBar() { return new Bar(); }

public:
    static void RegisterBar() {
        Factory::Instance()->RegisterObject(1, Bar::CreateBar);     
    }
};

// use the comma operator hack to register the create methods
int foodummy = (Foo::RegisterFoo(), 0);
int bardummy = (Bar::RegisterBar(), 0);

int main() {
    Factory::Instance()->CreateObject(0); // create foo object
    Factory::Instance()->CreateObject(1); // create bar object
}
2
Masked Man

Non, il n'y a aucun moyen d'hériter de la déclaration friend de la classe de base. Cependant, si vous créez le constructeur Baseprivate, il ne sera pas possible de créer des instances de classes dérivées sans l'aide de Factory.

1
Bartek Banachewicz

Comme d'autres l'ont déjà dit, l'amitié n'est pas héritable. 

cela ressemble à un bon candidat du motif "Abstract Factory".

supposons que les "SomeClass" dérivées de la base sont utilisées polymorphiquement. déclare une base abstraite de fabrique qui crée des objets Base. dérive chaque fabrique concrète de la base, substitue la méthode de création de la base ...

voir http://en.wikipedia.org/wiki/Abstract_factory_pattern pour des exemples

1
user1252446

Pour référence future, une autre idée est venue de chat entre OP et moi, qui ne fonctionne qu’avec une seule utilisation de friend comme le voulait le PO. Bien sûr, ce n'est pas une solution universelle, mais cela peut être utile dans certains cas. 

Le code ci-dessous est un code minimal qui montre les idées essentielles. Cela doit être "intégré" dans le reste du code Factory.

class Factory;

class Top { // dummy class accessible only to Factory
private:
    Top() {}
friend class Factory;
};

class Base {
public:    
    // force all derived classes to accept a Top* during construction
    Base(Top* top) {}  
};

class One : public Base {
public:    
    One(Top* top) : Base(top) {}
};

class Factory {
    Factory() {
        Top top;  // only Factory can create a Top object
        One one(&top);  // the same pointer could be reused for other objects
    }
};
0
Masked Man

Tu ne peux pas faire ça. Ceci est fait pour protéger l'encapsulation. Voir cet article: Pourquoi le C++ n'autorise-t-il pas l'amitié héritée?

0
Adri C.S.

Ce n'est pas possible. Comme d'autres l'ont dit, l'amitié n'est pas héritée. 

Une alternative consiste à protéger tous les constructeurs de hiérarchie de classes et à ajouter la fonction/classe fabrique comme ami à toutes les classes qui vous intéressent.

0
Nik-Lz