Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way for creating a generic object in C++?

Having programmed mostly in C# I find myself in a loss when it comes to C++. I however need to create a C++ app, as it is merely a component in a bigger C++ solution.

Situation

  • I have a struct (Parent) that include a data component (object). This could be a data component of any type - i.e. custom struct too.
  • Only the compiler of the Parent (who created the Parent incl object), and the end receiver needs to know the type of the data component inside the object, as the data is only relevant to them.
  • The Parent struct however might pass through several methods, objects and even to other processes.
  • The data options for the object are limited, and both compiler and decompiler knows the different options of data types.. hence they are able to decompile the object in original form
  • The types however could be extended (i.e. though limited it is not necessarily fixed) and the decompiler and compilers updated in future

Problem - I do not want to recreate each method and object that might encounter this data component with a "template"... also seeing that the data type could in future be altered suggested that templates for each process that encounter the object in the long run is not ideal to start with.

I was looking for something similar to C# Object - and have written the following components

Question

  • Is this a good solution or may I encounter problems in the future
  • Is there an improvement that could be made on this? That I have not thought about (especially for not including the Object\impl.h?
  • Is there a totally different / better solution?

The Code

  • I have the following in my Header file

    struct Object
    {
        Object();
        // Return true if value has succesfully been set
        // Return false if there is no compatibility between Value and result.
        template <typename T>
        bool GetValue(T &result);
        template<typename T>
        bool SetValue(T value);
        virtual LPVOID GetObjectAddress() = 0;
        virtual const char* GetType() = 0;
    };
    
    template<typename T>
    struct ObjectType:public Object
    {
            ObjectType(T value);
            T Value;
            LPVOID GetObjectAddress();
            const char* GetType();
    };
    

I also have have a CreateType function for creating several ready made objects at start and for example purposes is called as follows at end of .h file

    template class CreateType<int>;

Bear in mind the int is just for example... it is actually different structs.

  • I also have another header file which is included at the bottom of this header file as follows:

    #include "Implementation\Object_impl.h"
    -> looks like this
    
    template<typename T>
    ObjectType<T>::ObjectType(T value)
    {
        Value = value;
    };
    
    template <typename T>
    // Return true if value has succesfully been set
    // Return false if there is no compatibility between Value and result.
    bool Object::GetValue(T &result)
    {
        if (typeid(result).name() == GetType())
        {
            result = *(T *)GetObjectAddress();
            return true;
        }
        return false;
    };
    
    template<typename T>
    bool Object::SetValue(T value)
    {
        if (typeid(T).name() == GetType())
        {
            *(T*)GetObjectAddress() = value;
            return true;
        }
        return false;
    };
    
    template<typename T>
    const char* ObjectType<T>::GetType()
    {
        return typeid(Value).name();
    }
    template<typename T>
    EXPOBJ LPVOID ObjectType<T>::GetObjectAddress(){
        return (LPVOID)&Value;
    }
    

I wish I could include most of this in a cpp file, but then I would not be able to create different object on demand... I am not yet sure what the implications would be... also to extend the types one only need to include the normal object header file.

I know inline could be an option, but also think it is not ideal?

At the moment however this compiles and works perfectly as a "generic" option. One could also extend by inheriting from inheriting from "Object"?

...oh and to use I just do this - which seemed to work:

    Object * a;
    a = new ObjectType<testing>(testing());
    testing x = testing();
    x.i = 50;
    a->SetValue(x);
    testing y = testing();
    testing &z = y;
    a->GetValue(z);

    cout << z.i << " for z and y = " << y.i << endl;

    Result -> 50 for z and y = 50
like image 721
DaClan Avatar asked Jul 17 '14 23:07

DaClan


1 Answers

Generally, we avoid anything remotely like an object type. If absolutely necessary (and it pretty much never is), we use boost::any.

As to your code: that's actually impressively good, but here's bits I'd mention:

You have no destructor. That's a MAJOR bug.

virtual ~Object() {}
//and
virtual ~ObjectType() {}

Also, GetObjectAddress is not type-safe.

class Object {
    //stuff
    template<class T>
    T* GetObjectAddress();
private: 
    virtual LPVOID GetRawPointer() = 0;
};    
template<class T>
inline T* Object::GetObjectAddress() 
{
    if (typeid(T).name() == GetType() || typeid(T).name()==typeid(void).name())
    {
        return static_cast<T*>(GetRawPointer());
    }
    return nullptr;
}

Also, I would ban the copy and move assignment bits, which helps prevents errors.

class Object {
    Object(const Object&) = delete;
    Object(Object&&) = delete;
    Object& operator=(const Object&) = delete;
    Object& operator=(Object&&) = delete;
    //stuff
};

I would give the derived type a default constructor, and conversion constructors

template<typename T>
struct ObjectType:public Object
{
    ObjectType() {}
    //C++11 here:
    template<class...Ts> 
    ObjectType(Ts...Vs) 
        :Value(std::forward<Ts>(Vs)...) {}
    //C++03 here:
    template<class first> 
    ObjectType(const first& f) : Value(f) {}
    template<class t0, class t1> 
    ObjectType(const t0& p0, const t1& p1) : Value(p0, p1) {}
    template<class t0, class t1, class t2> 
    ObjectType(const t0& p0, const t1& p1, const t2& p2) : Value(p0, p1, p2) {}
        //etc etc etc

Finally, use smart pointers instead of raw owning pointers. Raw pointers are fine, as long as they don't own the thing they point at.

std::unique_ptr<Object> a;
a.reset( new ObjectType<testing>() );
like image 56
Mooing Duck Avatar answered Nov 06 '22 21:11

Mooing Duck