I have been creating named variables in order to be able to pass their adress to a constructor that expects a pointer, but I want to be able to create them in a constructor or other function and then pass their address to the constructor that expects a pointer.
I am using C 20 and I have the following classes:
#include <iostream>
#include <string>
#include <vector>
#include <random>
using std::string, std::cout, std::cin, std::endl, std::vector;
class symbol {
public:
enum symbolKind {
null,
terminal,
sequence,
weighted,
random
};
protected:
symbolKind kind;
public:
virtual string evaluate() const = 0;
symbolKind getKind() {
return kind;
}
};
class nullSymbol : public symbol {
public:
nullSymbol() {
kind = symbol::null;
}
string evaluate() const override {
return "";
}
};
class terminalSymbol : public symbol {
private:
string termString;
public:
terminalSymbol(string pString) {
kind = symbol::terminal;
termString = pPhoneme;
}
string evaluate() const override {
return termString;
}
};
class sequenceSymbol : public symbol {
private:
vector<symbol*> symArray;
public:
sequenceSymbol(vector<symbol*> pArr) {
kind = symbol::sequence;
symArray = pArr;
}
string evaluate() const override {
string retStr = "";
for (symbol* current : symArray) {
retStr = current->evaluate();
}
return retStr;
}
};
class weightedSymbol : public symbol {
private:
float weight;
symbol* subSym;
public:
weightedSymbol(symbol* pSym, float pWeight) {
kind = symbol::weighted;
subSym = pSym;
weight = pWeight;
}
string evaluate() const override {
return subSym->evaluate();
}
float getWeight() {
return weight;
}
};
class randomSymbol : public symbol {
private:
vector<weightedSymbol*> symArray;
public:
randomSymbol(vector<weightedSymbol*> pArr) {
kind = symbol::random;
symArray = pArr;
}
string evaluate() const override {
float sum = 0.0;
for (weightedSymbol* current : symArray) {
sum = current->getWeight();
}
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_real_distribution<> dis(0.0, sum);
float randomResult = dis(gen);
float prev = 0;
for (weightedSymbol* current : symArray) {
if (randomResult < (prev = current->getWeight())) return current->evaluate();
}
}
};
I have been creating symbols like this:
terminalSymbol term_a("a");
terminalSymbol term_b("b");
sequenceSymbol seq_ab({ &term_a, &term_b});
cout << "ab test: " << seq_ab.evaluate() << endl;
But I would want to be able to do it like this or similar:
sequenceSymbol seq_ab_2({&terminalSymbol("a"), &terminalSymbol("b")});
cout << "ab test 2: " << seq_ab_2.evaluate() << endl;
This creates an error '&' requires l-value
in Visual Studio.
This is a pretty simple example, often there are a lot more variables being created than this. In this case, the addresses are being passed to the std::vector<weightedSymbol*>()
constructor; it's the same with the weightedSymbol()
constructor which also expects a pointer. This should work not only for the constructor (it doesn't even need to work with the constructor itself if there is another way to achieve the same functionality), but I want a way to create heap objects in a function and then return a pointer to them that works in this situation. It might be that I need to change the classes themselves for this to work, they should just provide the same functionality.
In the end, I want to create these symbol objects dynamically based on user input.
I have searched online and tried using a bunch of different things but didn't manage to get the functionality I want working. What would be a good way to implement this? There is probably a common technique/idiom that I can use, if so, please explain it to me in detail so that I can use it in other projects too.
CodePudding user response:
The objects you pass by pointers need to be destroyed somehow. In this snippet they will be destroyed automatically whenever you exit the block:
terminalSymbol term_a("a");
terminalSymbol term_b("b");
sequenceSymbol seq_ab({ &term_a, &term_b});
What should happen if you create objects without a named variable? Your classes never delete the objects that you pass by pointers, so that should be the caller responsibility to manage the lifespan of each object.
One solution for your problem is to wrap the objects into any sort of smart pointers. For example:
class sequenceSymbol : public symbol {
public:
sequenceSymbol(vector<shared_ptr<symbol>> pArr);
};
sequenceSymbol seq_ab_2({
std::make_shared<terminalSymbol>("a"),
std::make_shared<terminalSymbol>("b")
});