Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the best way to keep a pointer to different types when only one of them is needed at a time?

I'm in a situation where I need to have a pointer to an object of unknown type as a member of another class. But the list of valid object types are known at compile-time.

Say I have:

// A class templated on T
template<class T> class obj;

// Now assume there are some objects of types obj<...> somewhere else
obj<int> obj1;
obj<double> obj2;

// Now, manager needs to create a pointer for these objects
// in a specific manner at construction-time

// Say, we have to choose a canonical object (and set the other as secondary)

// manager shouldn't be templated on T because It needs
// to have multiple members of type obj<...>
template<class R>
class manager
{
    shared_ptr<...> canObjPtr;
    shared_ptr<...> secObjPtr;
 public:   
    // once set, canonical obj is not expected to change
    explicit manager(string canonicalObj);
}

How can I achieve this?

Some initial thoughts that don't really work:

  1. The most promising approach I can think of is to add T1 and T2 template arguments to manager and construct like this: manager<R, obj<int>, obj<double>>(). I get the feeling that I should grab the "canonicalObj" string with a static function before manager construction and then decide on which manager to create manager<R, obj<int>, obj<double>> or manager<R, obj<double>, obj<int>>.

  2. How about "templating" manager on obj1 and obj2 objects directly. Does it seem feasible? Note that I'm resilient to adding template parameters to manager because it's involved in some RunTime selection mechanism that doesn't like working multi-param templates.

  3. Instead of 2 member pointers, create 4 (See below, but this is lousy and will surely drive me crazy in the implementation: Need to always check if one pointer is null before using either of them)

template<class R> manager
{
    sharedPtr<obj<int>> canObjPtrI;
    sharedPtr<obj<float>> canObjPtrF;

    // same for secObjPtr above
 public:
    explicit manager(string canonicalObj);
}
  1. std::any and std::variant (and their boost equivalents) are out of the question because I want to keep using c++11 and can't use boost by policy. If I were to break one of the rules I would consider upgrading to c++17.

  2. I don't think the use of shared_ptr<void> for example can give any benefits because I would have to cast the pointer to the correct type anyway and can't use the interface of the object from the void pointer.

  3. The same can be said for union. It provides little to no improvement over 3.

Also, if you see this as a potential design problem, please don't hold back. I invite you to point out any flaws/improvements you notice.

[Edit] What this code is trying to do is ...

Basically, I need to choose a canonical and secondary objects just like I explained above from a list of pre-constructed obj<int> and obj<double> objects:

Based on user-input the class should decide on a canonical object and perform calculations based on this decision. I already have facilities to get references to these objects by their name (string). The only problem is that their types are different and making them inherit from a base class would constraint me to use the interface of that base only (is this correct?).

A minimal example as requested in comments

// A base template for objects defines common
// and possibly different interface.
template<class T> class objBase
{
protected:
    field<T> f_;

public:

    // Public data type
    using fieldType = field<T>;

    // New selects from "Registered childs of objBase"
    // at runtime
    static shared_ptr<objBase>::New() const;

    // Pure virtual to update the obj
    virtual void update() = 0;

    // Const-Access to field
    const field<T>& getField() const;
};

// Created objects are also registered automatically to a database
// i.e. I can get (const-)refs to them by querying the database
shared_ptr<objBase<int>> obj1 = objBase<int>::New();
shared_ptr<objBase<int>> obj2 = objBase<int>::New();
shared_ptr<objBase<float>> obj3 = objBase<float>::New();

// In manager, something like this needs to happen
template<class R>
class manager
{
private:
    // Read 2 target obj names from user input
    pair<string,string> objNames;

    // Possible types for requested objs:
    // obj_a, obj_b : both objBase<int> or both objBase<float>
    // or they can have different types 
    // But we essentially need only:
    pointer<...> canonicalObj;
    pointer<...> secondaryObj;

    // These should be used like this
    void useObjField() const
    {
        // Not using auto for clarity
        const decltype(*canonicalObj)::FieldType& objField
            = canonicalObj->getField();
        for(int i=0; i < objField.size(); ++i)
        {
            // Loop through elements and use them for some calculations
            // Related to other fields in manager
        }
    }
};
like image 678
Elwardi Avatar asked Nov 07 '22 05:11

Elwardi


1 Answers

std::any and std::variant (and their boost equivalents) are out of the question because I want to keep using c++11 and can't use boost by policy.

Still not a problem: You can use mpark::variant - it's C++11-compatible. There are also other such variant class implementations floating around. If you'd rather go with an any-like class (less recommended), try perhaps linb::any - same idea; C++11-compatible.

IIANM, these are both header-only library (ignore the test/example programs), so you don't even need any complex installation; you can just grab a recent version of the header, or be super-organized, build and install them properly with CMake, then use a CMake find_package() command to locate them.

Finally, a "qualified union" would be a rough alternative to using a variant. A variant is essentially a union and a variable which tells you which of the types in the union is the active one. I would not recommend this, for simplicity of use and safety - but it would likely mean a lot less code.

like image 147
einpoklum Avatar answered Nov 16 '22 05:11

einpoklum