What data structure should I use to model a database/table? - c++

Databases usually look like this
┃Name|Age|..┃
┠────┼───┼──┨
┃John│025│..┃
┃Carl│033│..┃
┃....│...│..┃
In this case I mean a table with a fixed column size and a variable size of unsorted rows which can be addressed by an id.
Is there a data structure in C++11 (or earlier) that can represent data like this?
I thought of a couple of ways of cheating such a structure, but none of them is perfect.
1. Separate std::vector
std::vector<std::string> name;
std::vector<unsigned int> age;
// Write
name.push_back("John");
age.push_back(25);
// Read
std::cout << "The first entry is (" << name[0] << " | " << age[0] << ")\n";
Defining a table with many columns takes a lot of markup, though, and writing to it by calling push_back on each std::vector is really tedeous.
2. std::vector of std::tuple
(std::pair would be enough in this case)
std::vector<std::tuple<std::string, unsigned int>> table;
// Write
table.push_back(std::make_tuple("John", 25));
// Read 1
std::string name;
unsigned int age;
std::tie(name, age) = table[0];
std::cout << "The first entry is (" << name << " | " << age << ")\n";
// Read 2
enum
{
NAME = 0,
AGE
}
std::cout << "The first entry is (" << std::get<NAME>(table[0]) << " | "
<< std::get<AGE>(table[0]) << ")\n";
(Sorry, if I messed something up here; I've known about the existence of std::tuple since yesterday)
This is fine, but reading from it takes a lot of markup, this time, when you have to define new variables that you want to put the values in. You could just do the std::tie to whatever variable you need the values at, but that becomes unreadable. The second method is almost perfect, but using implicit enums is not something I want to do in C++11.
3. std::vector of std::array
enum
{
NAME = 0,
AGE
}
std::vector<std::array<std::string, 2> table;
// Write
table.push_back({"John", "25"});
// Read
std::cout << "The first entry is (" << table[0][NAME] << " | " << table[0][AGE] << ")\n";
This is also pretty good, but it suffers the same problem as 2.2 did. Also this only allows std::string values. In exchange it offers shorter and nicer syntax, though.

I suggest a std::vector<Record> to hold your records.
Use std::map<key, vector_index> as an index into your records. This will enable you to access records by different search criteria without sorting the vector all the time.

One thing you have't considered is using a std::vector of some kind of associative array, be that a std::map or std::unordered_map.
This would allow you to inspect a given item in the vector (or other sequential container) using the database column names
item["Name"]
item["Age"]
and so on. You would have to use a variant or something like boost::any for the value, obviously.
If you wish to talk to a database in C++, and know at compile time the type the columns have (as you appear to from your suggestions) it might be worth considering ways to generate code with appropriate structures for you directly from the data base scehema. There are a couple of questions around this topic already here and here. What if the db value is null?

Since this question is still seeing activity after 5 years, I will answer it myself with the 5 years of programming experience since then.
There is no point in using std::array or std::tuple to represent a row. A struct is more expressive and has less boilerplate.
The question basically asks the difference between AOS (array of structs), SOA (struct of arrays) and hash maps. The choice between the two depends on what you want to do with the data and how much data there is.
Data is loaded cache lines of 64 bytes with automatic cache preloading when iterating over the data. Since the hash map is non-contiguous and thus cannot take advantage of the caching mechanism, it should only be preferred when individual entries are accessed based on their keys rather than iterating over the entire data set.
The choice between AOS and SOA depends on whether the columns are iterated over individually or together. When iterating separately, by using SOA we can fit more of the same data on the cache line by not looking at the other arrays. For both AOS and SOA, the preferable data structure is std::vector which is a simple contiguous data structure and thus the processor has a lot of information for on-the-fly optimizations.

#-------------------------------------------
#Try with this piece of code based on <map>
#-------------------------------------------
#include <iostream>
#include <map>
using namespace std;
//---
class myBook
{
public:
string ISBN;
string Title;
int Pages;
myBook();
myBook(const myBook &);
~myBook(){};
myBook &operator=(const myBook &pTr);
int operator==(const myBook &pTr) const;
int operator<(const myBook &pTr) const;
};
myBook::myBook()
{
ISBN = "";
Title = "";
Pages = 0;
}
myBook::myBook(const myBook &copyin)
{
ISBN = copyin.ISBN;
Title = copyin.Title;
Pages = copyin.Pages;
}
ostream &operator<<(ostream &output, const myBook &myBook)
{
output << myBook.ISBN << ':' << myBook.Title << ':' << myBook.Pages << std::endl;
return(output);
}
myBook& myBook::operator=(const myBook &pTr)
{
this->ISBN = pTr.ISBN;
this->Title = pTr.Title;
this->Pages = pTr.Pages;
return(*this);
}
int myBook::operator==(const myBook &pTr) const
{
if( this->ISBN != pTr.ISBN) return(0);
if( this->Title != pTr.Title) return(0);
if( this->Pages != pTr.Pages) return(0);
return(1);
}
//---------------------------
main()
{
map<string, myBook> BooksLibrary;
myBook book1;
//---
book1.ISBN="1243954-23";
book1.Title="Autobiography by Ben Franklin";
book1.Pages=432;
BooksLibrary["0001"] = book1;
//---
book1.ISBN="555-USA991";
book1.Title="I Robot by Isac Asimov";
book1.Pages=323;
BooksLibrary["0002"] = book1;
//---
for( map<string, myBook>::iterator ii=BooksLibrary.begin(); ii!=BooksLibrary.end(); ii++)
{
std::cout << (*ii).first << "|" << (*ii).second << endl;
}
//---
return(0);
}

Related

Is it possible to bind a function from a vector of objects to a std::function, or how would you access a function from a vector of objects this way

I have a two classes, one of which is a 'main' configuration, the other is a secondary configuration with a subset of some options from the main configuration. One of the 'main' configuration members is a vector of secondary configuration objects used to override the main configuration options if present. I can bind the 'main' configurations get methods to functions which I add to a vector and use to get those parameters in testing (not all options are tested every time, so I create a vector of set values which matches the vector of get methods which I compare.)
I would like to similarly bind the get methods from the secondary configurations, specifically the last (should be only) one in the vector, but I am unsure how to do this or if it is even possible. I have tried a number of tweaks that make sense to me, however I am starting to doubt if this would even be a good idea with the amount of convolution I am creating. If anyone could point me to how I would accomplish this, either by binding it in such a way that it calls the desired method from the first object in the vector or another way, that is what I am looking for. A heavily paired down example of what I am attempting to do is below.
#include <iostream>
#include <vector>
#include <functional>
using namespace std;
class secConfig{
private:
double data;
public:
secConfig(double);
double get_data();
};
secConfig::secConfig(double indata){
data = indata;
}
double secConfig::get_data(){
return data;
}
class config{
private:
std::vector<secConfig> secConfigs;
double normalData;
public:
config(double, vector<secConfig>);
double getData();
vector<secConfig> getVecData();
};
config::config(double inData, vector<secConfig> inVec){
normalData = inData;
secConfigs = inVec;
}
double config::getData(){
return normalData;
}
vector<secConfig> config::getVecData(){
return secConfigs;
}
int main(int argc, char const *argv[])
{
secConfig initial(66.6);
vector<secConfig> inConfigs;
inConfigs.push_back(initial);
cout << "Inconfig raw data: " << initial.get_data() << endl;
cout << "Inconfig vector data: " << inConfigs.back().get_data() << endl;
config mainConfig(55.5, inConfigs);
cout << "Main config raw data: " << mainConfig.getData() << endl;
cout << "Main config internal vec data: " << mainConfig.getVecData().back().get_data() << endl;
vector<function<double()>> getvals;
getvals.push_back(bind(&config::getData, &mainConfig));
cout << "Main config bound data: " << getvals[0]() << endl;
// Something like: getvals.push_back(bind(&config::getVecData::back::get_data, &mainConfig));
// So I can: cout << "Secondary (vectorized) config data : " << getvals[1]() << endl;
return 0;
}
std::bind is rarely the best or easiest way to achieve something.
Use a lambda instead.
getvals.push_back( [&]{ return mainConfig.getVecData().back().get_data(); } );

What is an efficient way to get e.g. a specific member of a class by its id in C++?

I need help understanding how I setup my programme correctly. I have a class that stores "Customers". I read those customers from a file into the programme as class members. Now at a different part of my programme I want to get the corresponding data of the customer (e.g.: age, city) by the customers unique id.
class Customers {
const int customerid;
std::string name;
int age;
std::string city;
}
How can I get access to my class member where customerid = 3?
Right now I was successful putting all the data from my file into an std::vector and by searching the std::vector with find_if. Is there an easier way to archive this?
I remember from php that I was just simply using a multi-array, where the key was the unique customerid.
$customers= array (
1=>array("name"=>"John", "age"=> 22, "city" > "New York"),
2=>array("name"=>"Peter", "age"=> 58, "city" > "London"),
3=>array("name"=>"Jason", "age"=> 25, "city" > "Melbourne")
);
I got my relevant information, for example the city of customerid = 3, this way:
echo $customers[3]["city"];
So what is the best way to do this in C++?
A std::set will store customers in such a way (usually balanced binary search tree) that you can add and retrieve in logarithmic time.
This has an advantage over std::map in that you do not need to separate the key (customer id) from the value (the customer).
We have to do a little boilerplate to allow comparison of int to Customer (and vice-versa) by declaring a is_transparent type in our comparison class for the set (C++14):
static int id_counter = 0;
struct Customer {
const int customerid = id_counter++;
std::string name = std::to_string(customerid);
int age{};
std::string city;
};
struct customer_comp
{
bool operator()(const Customer& lhs, const Customer& rhs) const
{
return lhs.customerid < rhs.customerid;
}
bool operator()(int id, const Customer& rhs) const
{
return id < rhs.customerid;
}
bool operator()(const Customer& lhs, int id) const
{
return lhs.customerid < id;
}
using is_transparent = bool; // so that we can compare int to Customer
};
This is so that we can do something like customers.find(1)->city.
And now you can use both to create your collection like so:
int main()
{
std::set<Customer, customer_comp> customers;
customers.insert(Customer{});
customers.insert(Customer{});
for(const auto& c : customers)
std::cout << c.customerid << std::endl;
}
Live Demo
You may also consider std::unordered_set that behaves similarly, although the underlying storage is a hash table, and your comparison struct will compare == instead of <. This has the further benefit of constant expected time insert and retrieval (although it may or may not actually be faster than std::set in practice).
Aside:
The representation of std::set I think is closest to a "real world" scenario where you'd use a database as a backing store. A database index (or often table as index) is implemented as a balanced tree of degree N (N is specific to implementation/tuning), and a balanced binary search tree is one example of such a tree. In reality the B Trees used by a database have a much higher branching factor.
struct CustomerData {
std::string name;
int age = -1;
std::string city;
};
these are the attributes of a customer.
std::unordered_map< int, CustomerData > customersData = {
{1, {"John", 22, "New York}},
{2, {"Peter", 58, "London}}
};
Above we have a map (unordered, because we don't care about order) from the int id to the data. The initialization looks a lot like python you may note.
Another way to add data to the map:
customersData[3] = {"Jason", 25, "Melborne"};
there are other ways.
Here we look up the id 3:
if (customersData.count(3) != 0) {
auto data = customersData[3];
std::cout << "Customer #3 is " << data.name << " age " << data.age << " from " << data.city << "\n";
}
you can also iterate:
for (auto[ id, data ] : customersData) {
std::cout << "Customer #" << id << " is " << data.name << " age " << data.age << " from " << data.city << "\n";
}
this being C++, there are small changes you can make to reduce overhead at a few spots, but it also makes the code more complex. I kept it simple.
My first thought is to use a std::map<int, Customers> instead of the vector. This way, you can search the map by calling find (example).
If the map gets bigger over time, you should consider switching to an std::unordered_map or maybe even to a database, instead of the file access (something like SQLite). This way, you use SQL-statements to filter by id, which is faster then loading and searching everything locally.
Yeah, as Yksisarvinen told you, a std::map could be a good option, or even just std::unordered_map<int, Customer> because I think any Customer is as important as any other.
If you have all of them in a file, you have to search them and figure out how are they stored, I don't think that's a good idea.
You can think something similar. You can make in memory database of your Customers class for each object in tuple and map it with key(id) from std::map or std::unordered_map container so you can later query it efficiently.
class Customers {
public:
using CustomerInfo = std::tuple<std::string,int,std::string>;
Customers(const int id, std::string name, int age,std::string city):customerid(id),name(name),age(age),city(city){
¦ m_custInfo.emplace(id,std::make_tuple(name,age,city));
}
std::optional<CustomerInfo>QueryCustomer(const int id){
¦ auto it = m_custInfo.find(id);
¦ if(it!=m_custInfo.end()){
¦ ¦ return it->second;
¦ }
¦ return {}; // same as std::nullopt;
}
private:
std::map<int, CustomerInfo> m_custInfo;
const int customerid;
std::string name;
int age;
std::string city;
};
int main() {
Customers cust(101, "Joy", 13, "Tokyo");
const auto ret = cust.QueryCustomer(101);
if (ret) {
auto[name, age, city] = *ret;
std::cout << std::endl
<< name << " " << age << " " << city;
}
return 1;
}
You can use std::map<int,CustomerData> either ordered or unordered map, depending on your need.
you can use map[customerid] to get the corresponding object. It's like caching the data in memory.

Container Class vs Class - C++

I'm new to programming and just come across this assignment
Create a container class family that holds an array of 20 person objects.
I have been looking on the internet as well as in my book, but I still can't figure out the difference between a Container Class and a Class in C++.
How could I create a family class and 20 person objects at the same time?
"Container class" is not some official term; it's just the word "class" with an English describing word next to it. The assignment is asking you to create a class that contains some other stuff; namely, an array of 20 person objects.
At its most basic, the result could be as simple as this:
class family
{
public:
person people[20];
};
In real life, you might do something like this instead:
#include <array>
using family = std::array<person, 20>;
It seems unlikely that every family (or even most families) has precisely 20 people in it, so I would personally end up just going with:
#include <vector>
std::vector<person> family;
… and manipulating the vector as appropriate.
C++ is an object oriented language that encourages you to "encapsulate". The first step of this is to group concepts into objects described in terms of their data values and the operations that can be performed on that data.
So one could define an Account class consisting of an id and balance and functions to deposit or withdraw currency. This definition forms the type, but when you "instantiate" (create an instance of) this type, i.e.
Account a;
then the variable a refers to an object of type Account in this scope.
Sometimes you need a class that can store and track objects of other types. This is what is sometimes referred to as a "container class" and typically it combines a store and a count.
Say we want to store some floats. We could just write:
float store[64];
std::cout << "Enter the first number: ";
std::cin >> store[0];
But how would we track how many floats we have? We would probably need a counter.,
float store[64];
int stored = 0;
std::cout << "Enter the first number: ";
std::cin >> store[0];
stored++;
std::cout << "Enter the second number: ";
std::cin >> store[1];
stored++;
This works, and it's not terribly difficult, but if you are writing a function that expects to take a store and it's size, how do you express that?
void myFunction(std::string label, float* store, int count);
This requires two arguments and it's not exactly explicit.
C++ is about encapsulation: this idea of a "store" with a count of the contents could be encapsulated into a class:
struct Store {
float store_[64] {};
int count_ {0};
};
this is a container. We can now write our function that takes an object that contains other values with a single parameter:
void myFunction(std::string label, Store& store); // & here = by reference
If this was 'C' you would write code that directly manipulated the values in the store:
store.store_[N] = 1;
store.count_++;
but that's messy, we didn't check there was room. In C++, we can encapsulate this into the class description with member functions and hide the member variables so that you have to go through our proscribed interface to manipulate the data.
#include <iostream>
class Store {
enum { MaxCount = 64 };
float store_[MaxCount] {};
size_t count_ = 0;
public:
// return the maximum number of elements we can store
size_t capacity() const { return MaxCount; }
// true/false: is the store empty?
bool empty() const { return count_ == 0; }
// return the current count
size_t size() const { return count_; }
bool add(float value) {
if (count_ >= capacity()) {
std::cerr << "store is full!\n";
return false;
}
store_[count_] = value;
++count_;
}
// reset
void clear() {
count_ = 0; // we don't actually need to change the store
}
// allow array-like usage
const float& operator[](size_t index) const { return store_[index]; }
float& operator[](size_t index) { return store_[index]; }
// provide bounds-checked array-ish access
float at(size_t index) const {
if (index >= count_)
throw std::invalid_argument("array index out of bounds");
return store_[index];
}
};
int main() {
Store store;
for (size_t i = 0; i < store.capacity(); ++i) {
std::cout << "Enter number #" << i << " or -ve to stop: " << std::flush;
float f = -1;
std::cin >> f;
std::cout << "\n" << f << "\n";
if (f < 0)
break;
store.add(f);
}
std::cout << "You entered " << store.size() << " values:";
for (size_t i = 0; i < store.size(); ++i) {
std::cout << ' ' << store[i];
}
std::cout << '\n';
}
Live demo: http://ideone.com/boE3Ki
They are telling you to create a class that acts as a container for an array. In programming, especially when you first start, you will see many elements called containers because your teacher wants you to view variables, classes, and arrays (among other programming tools) as containers for you to store data in.
Viewing programming this way makes is much easier to conceptualize information as you get into more complex ideas like pointers.
So, long answer short, create a class with the array inside it. If you are learning about constructors and overloading make sure you are initializing it based on the data you pass in. If not, it should be only a couple lines of code to complete the project.

c++ custom output stream with indentation

I'm having some trouble trying to implement a custom stream class to generate nicely indented code in an output file. I've searched online extensively but there doesn't seem to be a consensus on the best way to achieve this. Some people talk about deriving the stream, others talk about deriving the buffer, yet others suggest the use of locales/facets etc.
Essentially, I'm finding myself writing a lot of code like this:
ofstream myFile();
myFile.open("test.php");
myFile << "<html>" << endl <<
"\t<head>" << endl <<
"\t\t<title>Hello world</title>" << endl <<
"\t</head>" << endl <<
"</html>" << endl;
When the tabs start to add up it looks horrible, and it seems like it would be nice to have something like this:
ind_ofstream myFile();
myFile.open("test.php");
myFile << "<html>" << ind_inc << ind_endl <<
"<head>" << ind_inc << ind_endl <<
"<title>Hello world</title>" << ind_dec << ind_endl <<
"</head>" << ind_dec << ind_endl <<
"</html>" << ind_endl;
i.e. create a derived stream class which would keep track of its current indent depth, then some manipulators to increase/decrease the indent depth, and a manipulator to write a newline followed by however many tabs.
So here's my shot at implementing the class & manipulators:
ind_ofstream.h
class ind_ofstream : public ofstream
{
public:
ind_ofstream();
void incInd();
void decInd();
size_t getInd();
private:
size_t _ind;
};
ind_ofstream& inc_ind(ind_ofstream& is);
ind_ofstream& dec_ind(ind_ofstream& is);
ind_ofstream& endl_ind(ind_ofstream& is);
ind_ofstream.cpp
ind_ofstream::ind_ofstream() : ofstream() {_ind = 0;}
void ind_ofstream::incInd() {_ind++;}
void ind_ofstream::decInd() {if(_ind > 0 ) _ind--;}
size_t ind_ofstream::getInd() {return _ind;}
ind_ofstream& inc_ind(ind_ofstream& is)
{
is.incInd();
return is;
}
ind_ofstream& dec_ind(ind_ofstream& is)
{
is.decInd();
return is;
}
ind_ofstream& endl_ind(ind_ofstream& is)
{
size_t i = is.getInd();
is << endl;
while(i-- > 0) is << "\t";
return is;
}
This builds, but doesn't generate the expected output; any attempt to use the custom manipulators results in them being cast to a boolean for some reason and "1" written to the file. Do I need to overload the << operator for my new class? (I haven't been able to find a way of doing this that builds)
Thanks!
p.s.
1) I've omitted the #includes, using namespace etc from my code snippets to save space.
2) I'm aiming to be able to use an interface similar to the one in my second code snippet. If after reading the whole post, you think that's a bad idea, please explain why and provide an alternative.
The iostreams support adding custom data to them, so you don't need to write a full derived class just to add an indentation level that will be operated on by manipulators. This is a little-known feature of iostreams, but comes in handy here.
You would write your manipulators like this:
/* Helper function to get a storage index in a stream */
int get_indent_index() {
/* ios_base::xalloc allocates indices for custom-storage locations. These indices are valid for all streams */
static int index = ios_base::xalloc();
return index;
}
ios_base& inc_ind(ios_base& stream) {
/* The iword(index) function gives a reference to the index-th custom storage location as a integer */
stream.iword(get_indent_index())++;
return stream;
}
ios_base& dec_ind(ios_base& stream) {
/* The iword(index) function gives a reference to the index-th custom storage location as a integer */
stream.iword(get_indent_index())--;
return stream;
}
template<class charT, class traits>
basic_ostream<charT, traits>& endl_ind(basic_ostream<charT, traits>& stream) {
int indent = stream.iword(get_indent_index());
stream.put(stream.widen('\n');
while (indent) {
stream.put(stream.widen('\t');
indent--;
}
stream.flush();
return stream;
}
I have combined Bart van Ingen Schenau's solution with a facet, to allow pushing and popping of indentation levels to existing output streams. The code is available on github: https://github.com/spacemoose/ostream_indenter, and there's a more thorough demo/test in the repository, but basically it allows you to do the following:
/// This probably has to be called once for every program:
// http://stackoverflow.com/questions/26387054/how-can-i-use-stdimbue-to-set-the-locale-for-stdwcout
std::ios_base::sync_with_stdio(false);
std::cout << "I want to push indentation levels:\n" << indent_manip::push
<< "To arbitrary depths\n" << indent_manip::push
<< "and pop them\n" << indent_manip::pop
<< "back down\n" << indent_manip::pop
<< "like this.\n" << indent_manip::pop;
To produce:
I want to push indentation levels:
To arbitrary depths
and pop them
back down
like this.
I had to do a kind of nasty trick, so I'm interested in hearing feedback on the codes utility.

C++: Why is my vector of structs acting as one struct?

I'm working my way through Accelerated C++ and have decided to mess around with the one of structs that were defined in there. While doing so, I've come across a problem: creating a vector of these structs and modifying the elements in each one seems to modify the elements in all of them.
I realize that this probably means I've initialized all the structs in the vector to a struct at a single memory address, but I used the .push_back() method to insert "dummy" structs in to the vector. I was under the impression that .push_back() pushes a copy of its argument, effectively creating a new struct.
Here is the header for the struct:
#ifndef _STUDENT_INFO__CHAPTER_9_H
#define _STUDENT_INFO__CHAPTER_9_H
#include <string>
#include <iostream>
#include <vector>
class Student_info9{
public:
Student_info9(){homework = new std::vector<double>;};
Student_info9(std::istream& is);
std::string getName() const {return name;};
double getMidterm() const {return midterm;};
double getFinal() const {return final;};
char getPassFail() const {return passFail;};
std::vector<double> *getHw(){return homework;};
void setName(std::string n) {name = n;};
void setMidterm(double m) {midterm = m;};
void setFinal(double f) {final = f;};
private:
std::string name;
double midterm;
double final;
char passFail;
std::vector<double> *homework;
};
#endif /* _STUDENT_INFO__CHAPTER_9_H */
And here is the code that i'm fooling around with (excuse the excessive print statements... the result of some time trying to debug :) ):
vector<Student_info9> did9, didnt9;
bool did_all_hw9(Student_info9& s)
{
vector<double>::const_iterator beginCpy = s.getHw()->begin();
vector<double>::const_iterator endCpy = s.getHw()->end();
return(find(beginCpy, endCpy, 0) == s.getHw()->end());
}
void fill_did_and_didnt9(vector<Student_info9> allRecords)
{
vector<Student_info9>::iterator firstDidnt = partition(allRecords.begin(), allRecords.end(), did_all_hw9);
vector<Student_info9> didcpy(allRecords.begin(), firstDidnt);
did9 = didcpy;
vector<Student_info9> didntcpy(firstDidnt, allRecords.end());
didnt9 = didntcpy;
}
int main(int argc, char** argv) {
vector<Student_info9> students;
Student_info9 record;
for(int i = 0; i < 5; i++)
{
students.push_back(record);
}
for(int i = 0; i < students.size(); i++)
{
students[i].setMidterm(85);
students[i].setFinal(90);
students[i].getHw()->push_back(90);
std::cout << "student[" << i << "]'s homework vector size is " << students[i].getHw()->size() << std::endl;
students[i].getHw()->push_back(80);
std::cout << "student[" << i << "]'s homework vector size is " << students[i].getHw()->size() << std::endl;
students[i].getHw()->push_back(70);
std::cout << "student[" << i << "]'s homework vector size is " << students[i].getHw()->size() << std::endl;
std::cout << "Just pushed back students[" << i << "]'s homework grades" << std::endl;
if(i == 3)
students[i].getHw()->push_back(0);
}
std::cout << "student[3]'s homework vector size is " << students[3].getHw()->size() << std::endl;
for(vector<double>::const_iterator it = students[3].getHw()->begin(); it != students[3].getHw()->end(); it++)
std::cout << *it << " ";
std::cout << std::endl;
std::cout << "students[3] has " << ( ( find(students[3].getHw()->begin(),students[3].getHw()->end(), 0) != students[3].getHw()->end()) ? "atleast one " : "no " )
<< "homework with a grade of 0" << std::endl;
fill_did_and_didnt9(students);
std::cout << "did9's size is: " << did9.size() << std::endl;
std::cout << "didnt9's size is: " << didnt9.size() << std::endl;
}
As you can see by the print statements, it seems that the homework grades are being added only to one Student_info9 object, copies of which seem to be populating the entire vector. I was under the impression that if you were to use consecutive copies of .push_back() on a single object, it would create copies of that object, each with different memory addresses.
I'm not sure if that's the source of the problem, but hopefully someone could point me in the right direction.
Thanks.
When you push a StudentInfo onto the vector, it is indeed copied, so that's not the problem. The problem is the vector containing the homework grades. Since you only store a pointer to that vector in StudentInfo, only the pointer, not the vector, is copied when you copy a StudentInfo. In other words you have many different StudentInfos that all have a pointer to the same homework vector.
To fix this you should define a copy constructor which takes care of copying the homework vector.
Have you learned about the copy constructor yet? If so, think about what is happening with vector<Student_info9> students on push_back().
Specifically, what happens with this pointer.
std::vector<double> *homework;
The line Student_info9 record; constructs a Student_info9 using the first constructor. This first constructor creates a vector and stores a pointer to it as a member variable. You then proceed to add a copy of this Student_info9 to a vector 5 times. Each copy has a pointer to the same vector.
Your StudentInfo9 class contanis a pointer to a std::vector<double>, which means in the default copy constructor (which will be called when you add a StudentInfo9 object to your vector), the pointer itself is copied. That means all of your StudentInfo9 objects have the same homework vector.
Does that make sense? Please refer to http://pages.cs.wisc.edu/~hasti/cs368/CppTutorial/NOTES/CLASSES-PTRS.html for a more in depth look at pointers and copy constructors.