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;
}