Mäx Müller
Mäx Müller

Reputation: 253

C++ - How to update pointer (or members) between instances of same class


I have a simple class which consists of a void pointer and an int (this is some sort of a boost::Variant educational project).
I also have a working copy constructor and a destructor.

But what grinds my gears is, how I would accomplish something like this:

Container cont1("some value"); //simple construction
Container cont2;
cont2.createLink(cont1); //this should initialize members with a reference (or something alike)
std::cout<<cont1; //yields "some value"

cont2.set(20); //setting this container should update the original container too, since I initialized with a reference (or smth alike)
std::cout<<cont1; //yields 20

This is the simplified version of the class:

class Container {
    public:
        Container(){}
        Container(const std::string &val){var.type = STRING; var.data = new std::string(val);}
        Container(int val){ /* same for int */}
        Container(const Container &val){ /* do a memory copy */}

        void set(int val){ /* set the value if type matches, otherwise allocate a new pointer */}
        void set(const std::string &val){ /* the same as for int */}
        void createLink(const Container &val){ /* somehow assign a reference or whatsoever */}
    private:
        typedef struct VAR {
            int type = 0;
            void *data = NULL; }       
        VAR var;
}

If I set the value of cont2 to a string (i.e. the same data type it holds at the moment), everything is fine, because the set would not allocate a new pointer and rather assign a new value.
But how do I make sure the pointer of cont1 updates if I assign a different value to cont2 and therefore have to allocate a new pointer?

Would I need something like shared_pointer?

Thanks for any insight!

EDIT:

I changed to function name to make it more clear what should happen.

Upvotes: 1

Views: 294

Answers (2)

jxh
jxh

Reputation: 70402

There is a solution that only involves straight OO. You could create an interface for your variant type, and use double indirection to the variant instance to allow linked containers to share the same variant instance.

The reason double indirection is required is because of the way you want the set() method to automatically allocate a new variant instance if the new type doesn't match the original type. If we simply shared a pointer to the variant from both containers, then after set() creates a new variant instance, each container would be referring to different instances again.

To get around that, we can use a pointer to a pointer to a variant in the container instead.

Here is a possible way to define your variant interface, and how it could be subclassed:

typedef std::ostream Out;
struct BadType {};

struct Var {
    virtual ~Var () = default;
    virtual Out & print (Out &os) { return os << "(BadType)"; }
    virtual void set (int) { throw BadType(); }
    virtual void set (const std::string &) { throw BadType(); }
};

struct VarInteger : Var {
    int data;
    VarInteger (int v) : data(v) {}
    Out & print (Out &os) { return os << data; }
    void set (int v) throw() { data = v; }
};

struct VarString : Var {
    std::string data;
    VarString (const std::string &v) : data(v) {}
    Out & print (Out &os) { return os << data; }
    void set (const std::string &v) throw() { data = v; }
};

Here is how you could define your pointer to pointer, and how they could be initialized:

typedef std::shared_ptr<Var> VarPtr;
std::shared_ptr<VarPtr> varptr_;

static VarPtr make_var () { return std::make_shared<Var>(); }
static VarPtr make_var (int v) { return std::make_shared<VarInteger>(v); }
static VarPtr make_var (const std::string &v) {
    return std::make_shared<VarString>(v);
}

VarPtr & var () { return *varptr_; }
const VarPtr & var () const { return *varptr_; }

Container () : varptr_(std::make_shared<VarPtr>(make_var())) {}
Container (int v) : varptr_(std::make_shared<VarPtr>(make_var(v))) {}
Container (const std::string &v)
    : varptr_(std::make_shared<VarPtr>(make_var(v))) {}

And here is how your set() methods and createLink() method could be implemented.

void set (int v) {
    try { var()->set(v); }
    catch (BadType) { var() = make_var(v); }
}

void set (const std::string &v) {
    try { var()->set(v); }
    catch (BadType) { var() = make_var(v); }
}

void createLink (const Container &val) { varptr_ = val.varptr_; }

Demo

Upvotes: 1

Alexander Balabin
Alexander Balabin

Reputation: 2075

How about the following. Of course createLink cannot not take a const reference so I made it to take a non-const pointer.

class Container {
    const int STRING    = 0x0000001;
    const int INT       = 0x0000002;

    const int LINK      = 0x8000000;

public:
    ...    
    void set(int val){...}
    void set(const std::string &val)
    { 
        if (var.type == LINK) 
        {
            reinterpret_cast<Container*>(var.data)->set(val);
        }
        else
            ...
    }
    void createLink(Container* val)
    {
        var.data = val;
        var.type = LINK;
    }
private:
    typedef struct VAR {
        int type = 0;
        void *data = NULL;
    };
    VAR var;
};

There are a some important points to think about - relative lifetimes of the link and the linked is the most obvious one.

Upvotes: 0

Related Questions