Gemeinsame Basisklasse für Methoden mit unterschiedlicher Signatur



  • Hallo Leute,

    ich habe eine kurze design-technische frage. und zwar habe ich zwei klassen, welche einfache datentypen kapseln,

    class intVal {
    public:
      unsigned int getValue() const { return value; }
      void setValue(unsigned int val) { value = val; }
    private:
      unsigned int value;
    };
    
    class boolVal {
    public:
      bool getValue() const { return value; }
      void setValue(bool val) { value = val; }
    private:
      bool value;
    };
    

    zur laufzeit soll entschieden werden, welches konkrete objekt erzeugt wird, dh. ich bräcuhte eine gemeinsame basisklasse, um auf die methoden zuzugreifen:

    Base* pBase = new intVal(); // bzw. pBase = new boolVal();
    pBase->setValue(1234);      // bzw. pBase->setValue(true);
    

    hat jemand eine idee, wie ich das problem lösen könnte?
    mit templates funzt es ja leider nicht, weil ich dann die basisklasse als template ausführen müsste und dann bei der deklaration des zeigers auf die basisklasse bereits einen typ angeben muss - was ich ja zu diesem zeitpunkt noch nicht weiß.
    ich hätte schon an das visitor-designpattern gedacht, aber da fällt mir keine passende lösung damit ein..

    danke, MrGentleman



  • und wie wäre es mit dem Factory-Pattern?
    In c# gibt es für sowas interfaces...oder eben eine virtuelle "basisklasse" mit virtuellem dtor und leitest die beiden davon ab!

    Ka mir fällt sonst nix andres wirklich ein!



  • hmm...
    aber das problem ist doch weiterhin, dass ich auch wenn über ne factory mir die objekte boolVal bzw. intVal generiere, der zugriff über ne gemeinsame schnittstelle erfolgen muss - oder habe ich da irgend einen denkfehler ???



  • Das Thema wurde hier vor kurzem schon mal diskutiert...
    Die sauberste Lösung dafür wäre in dem Fall wohl ein Weg über die RTTI:

    boolVal *pBool;
    intVal  *pInt;
    
    if(pBool = dynamic_cast<boolVal*>(pBase))
    {
       pBool->setBoolVal(true);
    }
    if(pInt = dynamic_cast<intVal*>(pBase))
    {
       pInt->setIntVal(1234);
    }
    

    Das widerspricht zwar den Prinzipien der objektorientierten Programmierung, aber lässt sich manchmal eben nicht vermeiden...

    Grüße,

    Martin



  • ok...den dynamic-cast wollte ich eigentlich irgendwie vermeiden wenns möglich wäre...aber naja...hilft halt nichts 🙂

    herzlichen dank!



  • Ich habe da mal ein Progrämmchen für dich geschrieben 🙂
    Es benutzt zwar auch dynamic_cast, aber was soll's.
    Unten siehst du dann auch, wie man die Value-Klasse verwenden kann.

    #include <cassert>
    
    class ValueImpl
    {
    public:
    	virtual ~ValueImpl() { }
    	virtual ValueImpl* clone() const = 0;
    };
    
    template<typename T>
    class ConcreteValue : public ValueImpl
    {
    public:
    	ConcreteValue(const T& value)
    		: value_(value)
    	{
    	}
    
    	ConcreteValue* clone() const
    	{
    		return new ConcreteValue(value_);
    	}
    
    	const T& get() const
    	{
    		return value_;
    	}
    
    	void set(const T& value)
    	{
    		value_ = value;
    	}
    
    private:
    	T value_;
    };
    
    class Value
    {
    public:
    	Value(const Value& value)
    		: p_impl_(value.p_impl_->clone())
    	{
    	}
    
    	template<typename T>
    	explicit Value(const T& value)
    		: p_impl_(new ConcreteValue<T>(value))
    	{
    	}
    
    	~Value()
    	{
    		delete p_impl_;
    	}
    
    	template<typename T>
    	const T& get() const
    	{
    		ConcreteValue<T>* p_casted = dynamic_cast<ConcreteValue<T>*>(p_impl_);
    		assert(p_casted);
    		return p_casted->get();
    	}
    
    	template<typename T>
    	void set(const T& value)
    	{
    		ConcreteValue<T>* p_casted = dynamic_cast<ConcreteValue<T>*>(p_impl_);
    		assert(p_casted);
    		p_casted->set(value);
    	}
    
    	Value& operator =(const Value& rhs)
    	{
    		delete p_impl_;
    		p_impl_ = rhs.p_impl_->clone();
    		return *this;
    	}
    
    private:
    	ValueImpl* p_impl_;
    };
    
    int main()
    {
    	Value a(5);
    	Value b(42.0f);
    
    	// OK:
    	int valueA = a.get<int>();
    	a.set(100);
    	float valueB = b.get<float>();
    	b.set(200.0f);
    
    	// FÜHRT ZU FEHLERN, WEIL TYP FALSCH IST:
    	double x = a.get<double>();
    	a.set(100.0);
    	int y = b.get<int>();
    	b.set(42);
    
    	return 0;
    }
    


  • mann, mann, mann - du bist mein held 🙂

    herzlichen dank für die implementierung - das schaut für meine zwecke sehr ideal aus!

    herzlichen dank!



  • Boost.Any 🙄



  • @ThomasRiker:

    Wenn Du die Prüfung auf dynamische Debug-Asserts beschränkst kannst Du Dir noch das rumsuchen in der Vererbungshierarchie (jedenfalls im Releasemodus) sparen:

    template<typename T> 
        const T& get() const 
        { 
    		assert(typeid(*p_impl_) == typeid(ConcreteValue<T>));
            ConcreteValue<T>* p_casted = static_cast<ConcreteValue<T>*>(p_impl_); 
            return p_casted->get(); 
        } 
    
        template<typename T> 
        void set(const T& value) 
        { 
    		assert(typeid(*p_impl_) == typeid(ConcreteValue<T>));
            ConcreteValue<T>* p_casted = static_cast<ConcreteValue<T>*>(p_impl_); 
            p_casted->set(value); 
        }
    

    Gibt an den gleichen Stellen asserts im Debug-Modus und kracht an den gleichen Stellen im Release-Modus 😉



  • Ja, stimmt 😉
    Am besten würde man wohl in jedem Fall eine Exception werfen.



  • Oder man installiert sich endlich Boost um dieses endlose Boost-Nachprogrammieren zu stoppen. 😡


Anmelden zum Antworten