Reputation: 356
I have a program where I want to load Variables from a text file to use them as default variables.
The text file should look like this:
Name=No Name
Age=8
Gender=male
etc.
Is there a simpler way and if not how do I do that in the place with the question marks?
My Code look like this:
int Age;
std::string Name;
bool male;
if(f.is_open())
{
while (!f.eof())
{
getline(f, line);
if (line.find("Name=") == std::string::npos)
{
Name=?????;
continue;
}
else if (line.find("Gender=") == std::string::npos)
{
if(????? == "true"); then
male=true;
else
male=false;
continue;
}
else if (line.find("Age=") == std::string::npos)
{
Age=?????;
continue;
}
//etc. ...
}
f.close();
Upvotes: 0
Views: 1021
Reputation: 356
I tried to simplify the solution of @Ted Lyngmo: ... I think it is not the fastest way and not the best, but it is more simple and more short:
#include <sstream>
class loadVars
{
public:
std::string file;
loadVars() { }
//Input ->
loadVars(std::string Text) {
this->setFile(Text);
}
loadVars(std::istream& is) {
this->setFile(is);
}
friend void operator>>(std::istream& is, loadVars& lv) {
lv.file = std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
}
void setFile(std::string Text) {
this->file = Text;
}
void setFile(std::istream& is) {
this->file = std::string((std::istreambuf_iterator<char>(is)), std::istreambuf_iterator<char>());
}
//<-
std::string extract_to_first(std::string to) {
std::string line;
std::stringstream s_string = std::stringstream(this->file);
while (std::getline(s_string, line)) {
if(line.find("=") != std::string::npos) {
if(line.substr(0,line.find("=")) == to) {
return line.substr(line.find("=")+1);
}
}
}
return "-1";
}
};
Upvotes: 0
Reputation: 73444
Is there a simpler way?
You could use a serialization library, like cereal or Boost, as @JesperJuhl suggested.
However, I would strongly suggest to take a step back, and review your approach. You are asking for an improvement, but you don't have a good solution at this point, because Why is iostream::eof inside a loop condition considered wrong?
As I had written here, I will use std::getline()
as the loop condition instead of ios::eof()
, in order to parse the file, line by line.
How do I do that in the place with the question marks?
Then, for every line, I will tokenize it, based on a delimiter (equal sign in your case), in order to extract two tokens, the name of the variable and its default value. Read more about it in Parse (split) a string in C++ using string delimiter (standard C++)
Afterwards, I would use an if-else approach (You could use a switch statement instead) to check the name of the variable, and assign its default value to the actual variables of the program.
Full code example:
#include <iostream>
#include <string>
#include <fstream>
int main(void) {
std::string defaultName, gender;
int age;
std::ifstream infile("mytextfile.txt");
std::string line, varName, defaultValue;
std::string delimiter = "=";
while (std::getline(infile, line)) {
varName = line.substr(0, line.find(delimiter));
defaultValue = line.substr(line.find(delimiter) + 1);
if(varName == "Name") {
defaultName = defaultValue;
continue;
} else if(varName == "Age") {
age = std::stoi(defaultValue);
continue;
} else if(varName == "Gender") {
gender = defaultValue;
continue;
} else {
std::cout << "Unknown entry: " << line << std::endl;
}
}
std::cout << defaultName << ", " << age << ", " << gender << std::endl;
return 0;
}
Output:
No Name, 8, male
Upvotes: 1
Reputation: 117871
If you feel a need to write it yourself instead of using a ready library, you could use a std::unordered_map<>
and add some streaming and extraction support around it. Here's an example with comments in the code:
#include <string>
#include <unordered_map>
class KeyValue { // Key Value
std::unordered_map<std::string, std::string> m_kv{};
public:
// at() is used to get a reference to a Value given the supplied Key. It uses
// the function with the same name in the unordered_map.
inline std::string& at(const std::string& Key) { return m_kv.at(Key); }
inline const std::string& at(const std::string& Key) const { return m_kv.at(Key); }
// The "as<T>" function below is used to extract values from the map.
// The exact version of the function that will be used depends on the type
// you want to extract from the string. Explicit specializations of the function
// are declared outside the class.
// A generic conversion function to anything that can be constructed from a std::string
template<typename T>
T as(const std::string& Key) const {
return at(Key);
}
// A function to extract directly into a variable using the proper as<T>
template<typename T>
void extract_to(T& var, const std::string& Key) const {
var = as<T>(Key);
}
// A friend function to read from an input stream (like an open file) and
// populate the unordered_map.
friend std::istream& operator>>(std::istream&, KeyValue&);
};
// Explicit specializations of KeyValue::as<T>()
// floats
template<>
float KeyValue::as(const std::string& Key) const {
return std::stof(at(Key));
}
template<>
double KeyValue::as(const std::string& Key) const {
return std::stod(at(Key));
}
template<>
long double KeyValue::as(const std::string& Key) const {
return std::stold(at(Key));
}
// signed integers
template<>
int KeyValue::as(const std::string& Key) const {
return std::stoi(at(Key));
}
template<>
long KeyValue::as(const std::string& Key) const {
return std::stol(at(Key));
}
template<>
long long KeyValue::as(const std::string& Key) const {
return std::stoll(at(Key));
}
// unsigned integers
template<>
unsigned KeyValue::as(const std::string& Key) const {
return std::stoul(at(Key));
}
template<>
unsigned long KeyValue::as(const std::string& Key) const {
return std::stoul(at(Key));
}
template<>
unsigned long long KeyValue::as(const std::string& Key) const {
return std::stoull(at(Key));
}
// bool
template<>
bool KeyValue::as(const std::string& Key) const {
const std::string& val = at(Key);
if(val=="true" || val=="1") return true;
else if(val=="false" || val=="0") return false;
throw std::range_error("\"" + Key + "\" is neither true nor false");
}
// the friend function that extracts key value strings from a stream
std::istream& operator>>(std::istream& is, KeyValue& kv) {
std::string line;
// read one line at a time
while(std::getline(is, line)) {
auto pos = line.find('=');
if(pos == std::string::npos || pos == 0) {
// if '=' was not found (or found at pos 0), set the failbit on the stream
is.setstate(std::ios::failbit);
} else {
// if '=' was found, put the Key and Value in the map by
// using substr() to split the line where the '=' was found
kv.m_kv.emplace(line.substr(0, pos), line.substr(pos + 1));
}
}
return is;
}
With that in place, you can read a file and populate the variables that you've preferably put in a class
/ struct
. Example:
#include <fstream>
struct Variables {
std::string Name{};
unsigned int Age{};
std::string Gender{};
double PI{};
bool Hungry{};
bool Sad{};
Variables(const std::string& filename) {
std::ifstream is(filename);
if(is) {
KeyValue tmp;
is >> tmp; // stream the whole file into tmp
// extract values
tmp.extract_to(Name, "Name");
tmp.extract_to(Age, "Age");
tmp.extract_to(Gender, "Gender");
tmp.extract_to(PI, "PI");
tmp.extract_to(Hungry, "Hungry");
tmp.extract_to(Sad, "Sad");
} else throw std::runtime_error("Could not read \""+filename+"\".");
}
};
Example data file (vars.dat
):
Name=No name
Age=8
Gender=male
PI=3.14159
Hungry=true
Sad=false
...and a main example::
#include <iostream>
int main() {
try {
Variables var("vars.dat"); // open file and populate variables
std::cout << std::boolalpha
<< "Name: " << var.Name << "\n"
<< "Age: " << var.Age << "\n"
<< "Gender: " << var.Gender << "\n"
<< "PI: " << var.PI << "\n"
<< "Hungry: " << var.Hungry << "\n"
<< "Sad: " << var.Sad << "\n";
} catch(const std::exception& ex) {
std::cerr << ex.what() << "\n";
}
}
Upvotes: 1
Reputation: 1
I would not reinvent this. As suggested, libraries for serialization exist. Consider Boost.PropertyTree as an example and Boost can be helpful to learn in general.
Upvotes: -1