C++ several object serialization - c++

So I make a serialization of a single object but I had problem with several.
Here is the code:
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class MyTest
{
private:
string test;
public:
MyTest():test(""){};
void setTest(const string& test) {this->test = test;};
string getTest() const {return this->test;};
};
void writeToFile(const MyTest& m)
{
ofstream ofs("data.mbp", ios::app|ios::binary);
ofs.clear();
ofs.write((char *)&m, sizeof(m));
ofs.flush();
ofs.close();
return;
};
MyTest& readTest(MyTest& m,int num)
{
ifstream ifs;
ifs.open("data.mbp", ios::in|ios::binary);
for ( int i = 1 ; i <= num ; i++)
ifs.read((char *)&m, sizeof(m));
return m;
}
int main(int argc,char* argv[])
{
MyTest m,t;
m.setTest("Hello");
writeToFile(m);
t.setTest("World");
writeToFile(t);
t = readTest(t,1);
cout << t.getTest() << endl;
m = readTest(m,2);
cout << m.getTest() << endl;
return 0;
}
The problem is that I do not know how to write two or more objects in a binary file and after that how can I read them.
Does anybody know?
Thanks in advance.

I recommend you using Boost - Serialization for serializing objects in C++: http://www.boost.org/libs/serialization/

There are a lot of different ways of doing that. You need to select a file format first. Think about XML in the first hand. Serialization of complex data structures is better to base on some existing library rather than write it yourself from scratch. Search Inet for such libraries.

Related

How to model a book-library system in C using structs?

I am trying to learn C and C++ and I am struggling a bit with pointers. Concretely, I want to model a book-library system using some structs. A library could have more or less books, but we do not exactly the number. Below, I will post some snippet code:
This works but the number of books is known, not desired.
// library.h
#ifndef _LIBRARY_
#define _LIBRARY_
#include "book.h"
typedef struct {
int noBooks;
book books[10];
}library;
void addBook(book*, library*);
#endif
// library.c
#include "library.h"
void addBook(book* b, library* l) {
l->books[l->noBooks] = *b;
l->noBooks++;
}
// book.h
#ifndef _BOOK_
#define _BOOK_
#include <string.h>
#include <stdio.h>.
#include <stdlib.h>
typedef struct book_ {
int id;
char author[15];
char title[15];
}book;
void displayInfo(const book*);
#endif
//book.c
#include "book.h"
void displayInfo(const book* b) {
printf("Id: %d \n Author: %s \n Title: %s\n", b->id, b->author, b->title);
}
//main.cpp
#include<iostream>
extern "C" {
#include "book.h"
#include "library.h"
}
int main() {
std::cout << "Start" << std::endl;
book b1;
strcpy_s(b1.author, "Ab");
b1.id = 1;
strcpy_s(b1.title, "Ab");
book b2;
strcpy_s(b2.author, "Df");
b1.id = 2;
strcpy_s(b2.title, "Df");
library l1;
l1.noBooks = 0;
addBook(&b1, &l1);
addBook(&b2, &l1);
std::cout << l1.books[0].author << "\n";
std::cout << l1.books[1].author;
}
How should I modify it in order to add books without to know exactly the number (10 in this case)?
I tried more cases, for example:
// library.h
#ifndef _LIBRARY_
#define _LIBRARY_
#include "book.h"
typedef struct {
int noBooks;
book* books;
}library;
void addBook(book*, library*);
#endif
// library.c
#include "library.h"
void addBook(book* b, library* l) {
l->books[l->noBooks] = *b;
l->noBooks++;
}
Still, it does not work. Could you give me guidance/advice?
Thank you for your time!
To directly address the concern mentioned in your question, pointers aren't necessary for this program at all. At least not pointers that you need to write.
Given the discussion in the comments about what your code is, I'll chip in an answer that leans more into C++.
#include <iostream>
#include <string>
#include <vector>
class Book {
public:
Book() = default;
Book(int id, std::string auth, std::string title)
: m_id(id), m_author(auth), m_title(title) {}
friend std::ostream& operator<<(std::ostream& sout, const Book& book) {
return sout << "ID: " << book.m_id << "\nAuthor: " << book.m_author
<< "\nTitle: " << book.m_title << '\n';
}
private:
int m_id = 0;
std::string m_author;
std::string m_title;
};
class Library {
public:
void add_book(Book book) { m_books.push_back(book); }
std::size_t size() const { return m_books.size(); }
// The better approach would be to provide iterators so that you can access
// individial books from the library, much like you can with the vector,
// but that's a whole other level of work.
void print() const {
for (auto book : m_books) {
std::cout << book << '\n';
}
}
private:
std::vector<Book> m_books;
};
int main() {
Book b1(1, "Frank Herbert", "Dune");
Book b2(2, "Frank Herbert", "Dune Messiah");
Library l1;
l1.add_book(b1);
l1.add_book(b2);
l1.print();
std::cout << "Number of books in library: " << l1.size() << '\n';
}
Output:
ID: 1
Author: Frank Herbert
Title: Dune
ID: 2
Author: Frank Herbert
Title: Dune Messiah
Number of books in library: 2
As you can see, even a basic C++ (not C with std::cout, but idiomatic C++) implementation requires essentially a full re-write. And I noted one area where I really skimped. Your Library is just wrapping a container, and containers are typically built to be iterated through. But the effort of writing an iterator (it would just be wrapping the vector iterator) just didn't make sense for this answer.
For a toy program like this, you might be better off skipping the Library class completely and just declaring something like std::vector<Book> library; in your main() function and taking direct advantage of all the capabilities that std::vector provides.
The big takeaways in this snippet that will make the code easier to manage are:
Classes. Bundles your relevant data and functions together.
std::vector. A heap-allocated array that grows when needed and knows its own size.
std::string. Handles all the messy parts and far less error-prone.
Also on display is operator overloading. This allows you use your custom types as if they were native types. Much nicer than something like a display() function.
While all that C syntax is technically valid C++, philosophically it's completely wrong. You either want to learn C, or you want to learn C++. Choose one and stick with it. Blending the two the way you did will just lead to a bad time.
If you want to do this in C:
You can try to implement your own resizable library.
Something like this :
typedef struct {
int length;
int capacity;
book *books;
} library;
int add_book(library *lib, book *b) {
// Add a book to the library
assert(lib != NULL); // Check if library is valid
assert(b != NULL); // Check if book is valid
if(lib->length + 1 > lib->capacity) { // We run out of space for new books
lib->capacity *= 2;
lib->books = realloc(lib->books, lib->capacity * sizeof(book)); // Check for errors
lib->books[lib->length] = *b;
lib->length++;
printf("Increased library capacity and added book to library\n");
return 0;
}
else {
lib->books[lib->length] = *b;
lib->length++;
printf("Book added to library\n");
return 0;
}
}
If you want to do this in C++ (much easier):
You can use vector and just append values.
Here is a minimal implementation demonstrating how to dynamically add a book to your library and clean up the resources allocated:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct book {
int id;
char author[15];
char title[15];
};
struct library {
unsigned noBooks;
struct book *books;
};
// check for duplicate id?
struct library *library_add(struct library *l, struct book *b) {
if(!l) return NULL;
struct book *books = realloc(l->books, sizeof(struct book) * (l->noBooks + 1));
if(!books) {
printf("realloc failed\n");
return NULL;
}
l->books = books;
l->books[l->noBooks].id = b->id;
strcpy(l->books[l->noBooks].author, b->author);
strcpy(l->books[l->noBooks].title, b->title);
l->noBooks++;
return l;
}
void library_print(struct library *l) {
if(!l) return;
for(unsigned i = 0; i < l->noBooks; i++) {
printf("id = %d, author = %s, title = %s\n",
l->books[i].id,
l->books[i].author,
l->books[i].title
);
}
}
void library_destroy(struct library *l) {
if(!l) return;
free(l->books);
}
int main() {
struct library l = { 0, NULL };
library_add(&l, &(struct book) { 1, "Ab", "Ab"});
library_add(&l, &(struct book) { 2, "Df", "Df"});
library_print(&l);
library_destroy(&l);
return 0;
}

C++ alternative to singleton design when a function-only class needs to be initialize at least once?

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <random>
#include <map>
#include <math.h>
#include <cstring>
using namespace std;
class MathClass {
private:
size_t current_capacity;
double* logfact;
bool inited = false;
MathClass() {
current_capacity = 0;
logfact = new double[1];
logfact[0] = 0;
}
void calculateLogFact(int n) {
if (current_capacity >= n) return;
double* newLogfact = new double[n+1];
for (int i=0; i<=current_capacity; i++) newLogfact[i] = logfact[i];
for (int i=current_capacity+1; i<=n; i++) newLogfact[i] = newLogfact[i-1] + log(double(i));
delete[] logfact;
logfact = newLogfact;
}
double factorial(int n) {
cout << "n = " << n << "\n";
calculateLogFact(n);
for (int i=0; i<=n; i++) cout << int64_t(round(exp(logfact[i]))) << " ";
cout << "\n";
return exp(logfact[n]);
}
public:
static double factorial2n(int n) {
static MathClass singleton;
return singleton.factorial(2*n);
}
};
int main(int argc, char** argv)
{
cout << MathClass::factorial2n(10) << "\n";
return 0;
}
My library need to use an expensive function that needs to be initialized once before use (to pre-calculate some expensive values so that we don't have to calculate them every time). Currently, I use the singleton method above for this.
However, there are 2 problems:
Multi-threading: this will cause race conditions if 2 different threads call this function.
People don't like singleton
Other problems that I'm not aware of
What other design can I use to solve this problem? Pre-computing values is a must since this function needs to be fast.
I agree with comments: Why hide the fact that MathClass caches results from the user? I, as a potential user, see no real benefit, rather potential confusion. If I want to reuse previously cached results stored in an instance I can do that. You need not wrap the whole class in a singleton for me to enable that. Also there is no need to manually manage a dynamic array when you can use std::vector.
In short: The alternative to using a singleton is to not use a singleton.
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
#include <algorithm>
#include <random>
#include <map>
#include <math.h>
#include <cstring>
using namespace std;
class MathClass {
private:
size_t current_capacity;
std::vector<double> logfact;
bool inited = false;
void calculateLogFact(int n) {
if (logfact.size() >= n) return;
auto old_size= logfact.size();
logfact.resize(n);
for (int i=old_size; i<n; i++) logfact.push_back(logfact.back() + log(double(i)));
}
double factorial(int n) {
cout << "n = " << n << "\n";
calculateLogFact(n);
for (int i=0; i<=n; i++) cout << int64_t(round(exp(logfact[i]))) << " ";
cout << "\n";
return exp(logfact[n]);
}
public:
MathClass() {
logfact.push_back(0);
}
double factorial2n(int n) {
return factorial(2*n);
}
};
void foo(MathClass& mc) { // some function using previously calculated results
std::cout << mc.factorial2n(2);
}
int main(int argc, char** argv)
{
MathClass mc;
cout << mc.factorial2n(10) << "\n";
foo(mc);
}
I am not sure if the maths is correct, I didn't bother to check. Also inited and most of the includes seem to be unused.
Concerning "Multi-threading: this will cause race conditions if 2 different threads call this function." I would also not bother too much to bake the thread-safety into the type itself. When I want to use it single-threaded I do not need thread-safety, and I don't want to pay for it. When I want to use it multi-threaded, I can do that by using my own std::mutex to protect access to the mc instance.
PS: Frankly, I think the whole issue is caused by a misconception. Your MathClass is not a "function only" class. It is a class with state and member functions, just like any other class too. The "misconception" is to hide the state from the user and pretend that there is no state when in fact there is state. When using this class I would want to be in conctrol what results I can query because they are already cached and which results need to be computed first. In other words, I would provide more access to the class state, rather than less.

What is the problem I am having with using arrays with classes?

I have been working on a project for my computer science class and have encountered an issue with the code working. I am shown no error except when I try to compile and I get an error that reads:
Exception thrown: write access violation.
_Left was 0xCCCCCCCC.
The purpose of my project is to take a list of names from an external file, read them into an array, sort said array and then output the sorted list all while using a class for the code.
Here is a copy of my code and I would like to extend my gratitude to whoever can help me through my issue:
**Header File**
#include <iostream>
using namespace std;
class person
{
public:
person();
bool get(ifstream&);
void put(ofstream&);
private:
int capacity = 0;
string first_name[CAPACITY];
string last_name[CAPACITY];
int age[CAPACITY];
};```
**Header function definitions cpp file**
#include<iostream>
#include<string>
#include<fstream>
#include<cstdlib>
const int CAPACITY=20;
using namespace std;
#include "Person.h"
//Names constructor
//Postcondition both first name and last name initialized to zero
person::person()
{
first_name[CAPACITY] = "";
last_name[CAPACITY] = "";
age[CAPACITY]=0;
}
bool person::get(ifstream& in)
{
in >> first_name[CAPACITY] >> last_name[CAPACITY] >> age[CAPACITY];
return(in.good());
}
void person::put(ofstream &out)
{
out << first_name[CAPACITY] << last_name[CAPACITY] << age[CAPACITY];
}
**cpp file which holds main**
#include<iostream>
#include<cstdlib>
#include<fstream>
#include<string>
const int CAPACITY = 20;
using namespace std;
#include "Person.h"
void pop(string *xp, string *yp);
void sort(string name[CAPACITY], int count);
int main()
{
class person names[CAPACITY];
ifstream infile;
ofstream outfile;
string filename;
string name[CAPACITY];
int n = 0;
cout << "Enter the file name you wish to open" << endl;
cin >> filename;
infile.open(filename + ".txt");
outfile.open("Person_New.txt");
if (infile.fail())
{
cout << "The file requested did not open" << endl;
exit(1);
}
while (!infile.eof())
{
names[n].get(infile);
n++;
}
sort(name, CAPACITY);
for (int i = 0; i < CAPACITY; i++)
{
names[i].put(outfile);
}
cout << "The file has been created" << endl;
infile.close();
}
void pop(string *xp, string *yp)
{
string temp = *xp;
*xp = *yp;
*yp = temp;
}
void sort(string name[CAPACITY], int count)
{
int i, j;
for (i = 0; i < count - 1; i++)
{
for (j = 0; j < count - i - 1; j++)
{
if (name[j] > name[j + 1])
{
pop(&name[j], &name[j + 1]);
}
}
}
}
Once again Thank you for any support
It sounds to me like the compiler is getting upset that you are trying to write (i.e. assign a value) at an address that you do not have permission to access. I believe your constructor for the class person might be at fault because of how this class stores its variables, as well as the class header:
Constructor for the class person:
`person::person(){
first_name[CAPACITY] = "";
last_name[CAPACITY] = "";
age[CAPACITY] = 0;
}`
Class header for the class person:
`class person{
public:
//stuff
private:
int capacity = 0;
std::string first_name[CAPACITY];
std::string last_name[CAPACITY];
int age[CAPACITY];
//more stuff
}`
C++ is very specific about its naming conventions, so it makes a distinction between capacity and CAPACITY. Because of this, the variable CAPACITY is not defined within the Person.h file.
Also, because CAPACITY is set to a fixed value in your Person.cpp file, whenever you use first_name[CAPACITY], last_name[CAPACITY], or age[CAPACITY] to assign new values, you are only updating the values at the index equal to CAPACITY unless you update the value of CAPACITY itself. In the code you provided, CAPACITY is equal to 20, so your program attempts to update exclusively index 20 with each method call. This will likely cause issues since the person class only attempts to make its arrays on the runtime stack, with a size of 0 each.
Separately, it seems like you want an array of people, but it appears that you are attempting to use a single person object to store the names and ages of multiple people by making these all arrays. Instead, I would recommend making first_name, last_name, and age not arrays, but rather single variables. Then, you can manipulate an array of type person using your CAPACITY variable. You got pretty close, but you can instead declare it as person myPersonArray[CAPACITY] (no need to mention "class" in front of it -- just be sure that you have #include "Person.h" in your main.cpp file). When you want to update a specific person, you can perform an operation like myPersonArray[updateThisIndexNum].update(newFirstName, newLastName, newAge) or some logical equivalent.
As a final note, I almost always highly recommend against using !infile.eof() to control your while loop when reading any file because eof() only indicates whether you have tried to read past the end of an input file. I would highly recommend checking out this post on Stack Overflow where people far more knowledgeable than I explain exactly why this is usually dangerous and how to avoid it.

Implementing SystemC TLM Testbench Build Fail

I am trying to implement a SystemC basic TLM test bench for an adder module I created using basic simple_initiator_socket and simple_target_socket.
Currently the build is failing and I am having trouble diagnosing why.
Here are the implementations for the three main modules, the adder, the test bench, and the main module that instantiates both and initiates dataflow.
main.cc
#include "systemc.h"
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
#include "tlm_utils/tlm_quantumkeeper.h"
using namespace sc_core;
using namespace sc_dt;
using namespace std;
#include "test_bench.h"
#include "adder.h"
SC_MODULE(Top)
{
test_bench *tb;
adder *ad;
sc_signal<bool> rst;
Top(sc_module_name name) :
rst("rst")
{
tb = new test_bench("test_bench");
ad = new adder("adder");
tb->socket.bind(ad->socket);
}
};
int sc_main(int argc, char *argv[])
{
Top *top = new Top("Top");
}
test_bench.cc
#define SC_INCLUDE_DYNAMIC_PROCESS
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
using namespace sc_core;
using namespace std;
using namespace sc_dt;
#include "test_bench.h"
#include <fstream>
#include <iostream>
test_bench::test_bench(sc_module_name name):
sc_module(name), socket("socket")
{
SC_THREAD(run_tests);
}
void test_bench::run_tests()
{
ifstream infile("../adder.golden.dat");
ofstream ofs;
ofs.open("../adder.dat", ofstream::out | ofstream::app);
while(infile >> data[0] >> data[1])
{
tlm::tlm_generic_payload *trans = new tlm::tlm_generic_payload;
sc_time delay = sc_time(10, SC_NS);
trans->set_data_ptr((unsigned char*)data);
socket->b_transport(*trans, delay);
ofs << data[0] << data[1] << data[2];
delete trans;
}
infile.close();
ofs.close();
printf ("Comparing against output data \n");
if (system("diff -w sha1.dat sha1.golden.dat"))
{
cout << "*******************************************" << endl;
cout << "FAIL: Output DOES NOT match the golden output" << endl;
cout << "*******************************************" << endl;
}
else
{
cout << "*******************************************" << endl;
cout << "PASS: The output matches the golden output!" << endl;
cout << "*******************************************" << endl;
}
}
adder.cc
#define SC_INCLUDE_DYNAMIC_PROCESS
#include "tlm_utils/simple_initiator_socket.h"
#include "tlm_utils/simple_target_socket.h"
using namespace sc_core;
using namespace std;
#include "adder.h"
adder::adder(sc_module_name name)
: sc_module(name), socket("socket")
{
socket.register_b_transport(this, &adder::b_transport);
socket.register_transport_dbg(this, &adder::transport_dbg);
}
void adder::b_transport(tlm::tlm_generic_payload& trans, sc_time& delay)
{
tlm::tlm_command cmd = trans.get_command();
sc_dt::uint64 addr = trans.get_address();
uint32_t *ptr = (uint32_t*)trans.get_data_ptr();
unsigned int len = trans.get_data_length();
unsigned char *byt = trans.get_byte_enable_ptr();
unsigned int wid = trans.get_streaming_width();
addend1 = *ptr;
addend2 = *(ptr++);
add();
memcpy(ptr + sizeof(uint32_t) * 2, (char*) &sum, sizeof(uint32_t));
}
unsigned int adder::transport_dbg(tlm::tlm_generic_payload& trans)
{
return 0;
}
void adder::add()
{
sum = addend1 + addend2;
}
Here is the error I am seeing upon compilation.
In file included from
/home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/sysc/kernel/sc_module.h:35:0,
from /home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/systemc:74,
from /home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/tlm:23,
from /home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/tlm_utils/simple_initiator_socket.h:23,
from /home/test_benches/adder/test_bench.cc:3:
/home/test_benches/adder/test_bench.cc:
In constructor ‘test_bench::test_bench(sc_core::sc_module_name)’:
/home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/sysc/kernel/sc_module.h:463:29:
error: ‘SC_CURRENT_USER_MODULE’ has not been declared
SC_CURRENT_USER_MODULE, \
/home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/sysc/kernel/sc_process.h:151:46: note: in definition of macro ‘SC_MAKE_FUNC_PTR’
static_cast(&callback_tag::func)
/home/epi/jfrye_xilinx/SystemC/systemc-2.3.2/include/sysc/kernel/sc_module.h:461:5:
note: in expansion of macro ‘declare_thread_process’
declare_thread_process( func ## _handle, \
/home/test_benches/adder/test_bench.cc:17:2: note: in expansion of
macro ‘SC_THREAD’ SC_THREAD(run_tests);
make: ***
[/home//test_benches/adder/obj/test_bench.o]
Error 1
My best guess is that I did not set up the sockets correctly. The test bench has a simple_initiator_socket and the adder has a simple_target_socket. Do I need to register the simple_target_socket with a b_transport method for the module? I did so in the initiator but in the tutorial below I did not see a requirement to do so for the target. My guess was the dataflow was like this:
simple_initiator_socket (member of test_bench) registered to b_transport method of module and simple_target_socket of another module (in top module)
Initiator module (test_bench) sets up tlm_generic_payload with data it needs to send to target (adder)
b_transport method of simple_initiator_socket (member of test_bench) called with tlm_generic_payload being passed (with addends for adder)
Target socket (target) receives and decodes tlm_generic_payload (addend values) that was passed.
Target socket (adder) performs operations (adds decoded addends) and modifies the tlm_generic_payload (passed by value) (by writing the computed sum back to the payload memory)
Initiator (test_bench) looks at modified tlm_generic_payload (now contains sum) and does some process (checks against theoretical sum)
I was trying to follow this example.
https://www.doulos.com/knowhow/systemc/tlm2/tutorial__1/
UPDATE
test_bench.h
class test_bench:
public sc_core::sc_module
{
public:
tlm_utils::simple_initiator_socket<test_bench> socket;
sc_out<bool> irq;
test_bench(sc_core::sc_module_name name);
void run_tests();
private:
uint32_t data[3];
};
There are two ways to declare modules in SystemC.
The first one is through using SC_MODULE macro:
SC_MODULE(mymodule) {
SC_CTOR(mymodule)
{
}
};
And the second one without it:
class mymodule : public sc_core::sc_module {
SC_HAS_PROCESS(mymodule);
public:
mymodule(sc_core::sc_module_name)
{ }
};
I would prefer the second one because:
It avoids those nasty macros as much as possible.
It allows you to inherit from another module.
Now why you need SC_MODULE or SC_HAS_PROCESS macros. The reason is that macros SC_METHOD and SC_THREAD need to know type of module they are being used from to do their job. Since SystemC is based on old revision of C++ language released in 1998, there was no way to do this automatically. So helper macro SC_HAS_PROCESS was defined as:
#define SC_HAS_PROCESS(user_module_name) typedef user_module_name SC_CURRENT_USER_MODULE
This allows SC_METHOD and SC_THREAD to use SC_CURRENT_USER_MODULE as synonym for module they are being used in. Macro SC_MODULE already uses SC_HAS_PROCESS behind the curtain.
Another advise - if you are using C++11 compatible compiler you can declare a helper macro for yourself:
#define DECL(name, ...) name{#name, __VA_ARGS__}
This can help you declare named objects without typing their name twice:
sc_in<bool> DECL(clk);
Any error message mentioning this port with contain proper name for it.
Can also be used in constructor to initialize member field:
mymodule(sc_core::sc_module_name)
: DECL(clk)
{
}

Accessor Method to view private variable based on argument in a class in c++?

My problem is that I have many variables in my class and I want them to be accessed via an accessor method. Of course I could have several accessor functions to output my private variables but how can I make it so I can access any of them via an argument. My class:
class Character {
public:
void setAttr(string Sname,int Shealth,int SattackLevel,int SdefenseLevel) {
name = Sname;
health = Shealth;
attackLevel = SattackLevel;
defenseLevel = SdefenseLevel;
}
int outputInt(string whatToOutput) {
return whatToOutput //I want this to either be health, attackLevel or defenseLevel
}
private:
string name;
int health;
int attackLevel;
int defenseLevel;
};
Basically what I want to know is how do I return a private variable in regards to the outputInt function. Most OOP tutorials have one function to return each variable which seems like a very unhealthy thing to do in a large program.
C++ doesn't support what you try to accomplish: reflection or detailed runtime information about objects. There is something called "Run-Time Type Information" in C++, but it can't provide information about your variable name: the reason is because, in the compiled and linked binary this information (names of your variables) will not be necessarily present anymore.
However, you can accomplish something close to that, using i.e. std::unordered_map instead of plain integer variables. So it's possible to access values by their names, as strings.
Please consider the following code:
#include <string>
#include <iostream>
#include <unordered_map>
using namespace std;
class Character {
public:
void setAttr(const string& Sname, int Shealth, int SattackLevel, int SdefenseLevel) {
name = Sname;
values.insert(std::make_pair("health", Shealth));
values.insert(std::make_pair("attackLevel", SattackLevel));
values.insert(std::make_pair("defenseLevel", SdefenseLevel));
}
int outputInt(const string& whatToOutput) {
return values.at(whatToOutput);
}
private:
string name;
std::unordered_map<std::string, int> values;
};
int main(int argc, char* argv[]) {
Character yourCharacter;
yourCharacter.setAttr("yourName", 10, 100, 1000);
std::cout << "Health: " << yourCharacter.outputInt("health") <<std::endl;
std::cout << "Attack level: " << yourCharacter.outputInt("attackLevel") << std::endl;
std::cout << "Defense level: " << yourCharacter.outputInt("defenseLevel") << std::endl;
return 0;
}
It will output as expected:
Health: 10
Attack level: 100
Defense level: 1000
Another option without dependency on unordered_map would be, to use predefined static strings for your variable names and an array or vector for your values. So we could replace the class Character above with something like:
static std::string variableNames[3] = {
"health",
"attackLevel",
"defenseLevel"
};
class Character {
public:
void setAttr(const string& Sname, int Shealth, int SattackLevel, int SdefenseLevel) {
name = Sname;
variableValues[0] = Shealth;
variableValues[1] = SattackLevel;
variableValues[2] = SdefenseLevel;
}
int outputInt(const string& whatToOutput) {
int retVal = 0;
for (size_t i = 0; i < sizeof(variableNames)/sizeof(std::string); ++i) {
if (!whatToOutput.compare(variableNames[i])) {
retVal = variableValues[i];
}
}
return retVal;
}
private:
string name;
int variableValues[3];
};
And getting still same output. However, here you have to manage a list with all your variable names inside the string array manually - I don't like this solution and would prefer one of the others above personally.
Most common ways in C++ to handle such a design is to have seperate getHealth(), getAttackLevel(), getDefenseLevel() functions instead. However, this will miss one use-case, which is: if you want to let the user input a string, like i.e. "health" and display the corresponding variable then, you would need to write code by yourself to call the corresponding getXXX() function. If this is not a issue in your case, consider the following code which is much cleaner:
#include <string>
#include <iostream>
using namespace std;
class Character {
public:
void setAttr(const string& Sname, int Shealth, int SattackLevel, int SdefenseLevel) {
name = Sname;
health = Shealth;
attackLevel = SattackLevel;
defenseLevel = SdefenseLevel;
}
int getHealth() const { return health; }
int getAttackLevel() const { return attackLevel; }
int getDefenseLevel() const { return defenseLevel; }
private:
string name;
int health, attackLevel, defenseLevel;
};
int main(int argc, char* argv[]) {
Character yourCharacter;
yourCharacter.setAttr("yourName", 10, 100, 1000);
std::cout << "Health: " << yourCharacter.getHealth() <<std::endl;
std::cout << "Attack level: " << yourCharacter.getAttackLevel() << std::endl;
std::cout << "Defense level: " << yourCharacter.getDefenseLevel() << std::endl;
return 0;
}
One other unrelated advice: Instead of using string as parameter types for your functions, use const string& (const reference to string; see my example code above). This allows easier calling of your functions (they can be called directly with an string literal without the need to create additional variables in the calling code) and they will not make a additional unnecessary copy. The only copy then will take place at: name = Sname; (in your code two copies took place).
I don't know if it can be a good idea for you, but you can use a public typedef struct that you pass by reference and set your value.
class Character {
public:
//...
typedef struct allvalues{
string vname;
int vhealth;
int vattackLevel;
int vdefenseLevel;
}allvalues;
void getValues(allvalues& val){
val.vname = name;
val.vhealth = health;
val.vattackLevel = attackLevel;
val.vdefenseLevel = detenseLevel;
}
//...
};
//...
//somewhere in the code
Character myCarac;
//...
//Here how to use it
Character::allvalues values;
myCarac.getValues(values);