Home > Software engineering >  pointer pointing to a class object created from template for later use?
pointer pointing to a class object created from template for later use?

Time:08-05

Description:
I was trying to create a class object based on the user input. I tried to store its reference with a pointer and use it at a later stage. A similar idea with the variable type late in Dart language. Is it possible to have this operation in CPP and if so, what kind of variable type should I use?

Code:

#include <iostream>
#include <vector>
using namespace std;

template <typename T>
class foo {
    public:
        T item;
        foo(){}
};

class bar_1 {
    public:
        int value = 99;
        void randomise();
};

class bar_2{
    public:
        string value = "init";
        void randomise();
};

int main()
{
    int i;
    cin >> i;
    void* ptr;
    if (i > 0) {
        ptr = new foo<bar_1>;
    }
    else {
        ptr = new foo<bar_2>;
    }

    // maybe run the function to randomise 'value'
    // ptr->item.randomise();
    cout << ptr->item.value;
    return 0;
}

Error:

cout << ptr->item.value;
|          ^~
error: ‘void*’ is not a pointer-to-object type

CodePudding user response:

Definitely not in the way dynamic languages like JS and (AFAIK) Dart allow.

C is statically typed, so by the time you write cout << ptr->item.value in a (non-template) function, the types of item and value must be known. No way conflating unrelated types like int and string at runtime.

However, you may be able to use inheritance to achieve desired effect. A pointer to an object may always be converted to a pointer to its (accessible, i.e. public in most cases) base, and accessed like that base—but the object will retain its actual type, and all virtual methods of base will act on that type, and can be overriden:

class foo {
public:
    virtual ~foo() = default; // optional but highly recommended
    virtual randomize() {
        // the default implementation
    }
    // or: virtual randomize() = 0; // if you want ALL subclasses to override it
};

class bar_1: public foo {
public:
    int value = 99;
    void randomize() override {
        // the bar_1-specific implementation
    }
};

class bar_2: public foo {
public:
    std::string value = "something";
    void randomize() override {
        // the bar_2-specific implementation
    }
};

...

foo *obj = new bar_1(); // create an object of type bar_1, but treat it as foo
obj->randomize(); // will call bar_1::randomize as obj points to an object of type bar_1
// obj->value = 42; // won’t work: value is not a member of foo
delete obj;

obj = new bar_2(); // now, create an object of type bar_2, but treat it as foo again
obj->randomize(); // will call bar_2::randomize as obj now points to an object of type bar_2
delete obj;

Or with smart pointers (highly recommended):

std::unique_ptr<foo> obj = std::make_unique<bar_1>()
// std::unique_ptr<foo> obj{new bar_1()}; // if you can’t afford C  17
obj->randomize();

obj = std::make_unique<bar_2>();
// obj.reset(new bar_2()); // if you can’t afford C  17
obj->randomize();

CodePudding user response:

As comments have pointed out, you can't use a pointer to a variable declared in a block after the end of that block. You could fix this with a std::unique_ptr, or possibly std::optional.

Here are two possible solutions.

#1: A std::variant<foo<bar_1>, foo<bar_2>> can hold an object of either type, without requiring any changes to your existing classes (and without requiring any dynamic allocations). Then we can use std::visit to do things on whichever object it contains.

#include <variant>
#include <optional>
int main()
{
    int i;
    std::cin >> i;
    std::optional<std::variant<foo<bar_1>, foo<bar_2>>> the_foo;
    if (i > 0) {
        the_foo = foo<bar_1>{};
    }
    else {
        the_foo = foo<bar_2>{};
    }

    // run the function to randomise 'value'
    std::visit([](auto &foo) { foo.item.randomise(); }, *the_foo);
    std::visit([](auto &foo) { std::cout << foo.item.value; }, *the_foo);
    return 0;
}

#2 If you can change the classes, notice that bar_1 and bar_2 both contain some common operations "randomise item" and "print item". So we can create an interface allowing polymorphic use of those operations without knowing the actual type. This is also more easily extensible if you add additional similar classes later.

class IBar {
public:
    virtual ~IBar() = default;
    virtual void print(std::ostream& os) const = 0;
    virtual void randomise() = 0;
};

class bar_1 : public IBar {
public:
    int value;

    void print(std::ostream& os) const override
    { os << value; }

    void randomise() override;
};

class bar_2 : public IBar {
public:
    std::string value;

    void print(std::ostream& os) const override
    { os << value; }

    void randomise() override;
};

Now foo doesn't even need to be a template. It can just use an interface pointer instead of a member of the actual type:

#include <memory>
class foo {
public:
    std::unique_ptr<IBar> pItem;
    explicit foo(std::unique_ptr<IBar> p) : pItem(std::move(p)) {}
    foo() = default;
};

int main()
{
    int i;
    std::cin >> i;
    foo the_foo;
    if (i > 0) {
        the_foo.pItem = std::make_unique<foo<bar_1>>();
    }
    else {
        the_foo.pItem = std::make_unique<foo<bar_2>>();
    }

    // run the function to randomise 'value'
    the_foo.pItem->randomise();
    the_foo.pItem->print(std::cout);
    return 0;
}
  • Related