I am creating a custom linked list class to store strings from a program I created for an assignment. We were given a linked list handout that works for ints and were told to retool it for string storage, however I am running into an error when trying to run it.
I'm getting the error ""terminate called after throwing an instance of 'std::logic_error'
what(): basic_string::_S_construct null not valid"" (which I searched around and found it was because of a string being set to null, however I do not know how to fix the error, I'm guessing it is with line 8 but I've toyed around with it to no success.) I've searched around and looked through the similar questions but could not find anything that helped.
#include <cstdlib>
#include <iostream>
#include <string>
#include <cstdio>
#include <iomanip>
using namespace std;
struct node {
node(string current) { data=current; next=NULL; }
string data;
node *next;
};
class list {
public:
list(int N=0, string current);
~list();
bool empty() const { return N == 0; }
void clear();
void insert(int, const string &);
void push_front(const string ¤t);
friend ostream & operator<<(ostream &out, const list ¤t);
private:
int N;
node *head;
node *findnode(int);
};
list::list(int M, string current) {
N = M;
head = new node;
for (int i=0; i<N; i++)
insert(0, current);
}
list::~list() {
clear();
delete head;
}
void list::clear() {
while (!empty()) remove(0);
}
void list::insert(int i, const string &din) {
node *p = new node(din);
node *pp = findnode(i-1);
p->next = pp->next;
pp->next = p;
N++;
}
inline
node *list::findnode(int i) {
if (i == -1)
return head;
node *p = head->next;
while (i--)
p = p->next;
return p;
}
void list::push_front(const string ¤t) {
head = new node;
head->next;
}
ostream& operator<<(ostream& out, const list& current)
{
out << current;
return out;
}
const string rank[] = { "Ace", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Jack", "Queen", "King" };
const string suit[] = { "Clubs", "Diamonds", "Hearts", "Spades" };
string random_card(bool verbose=false) {
string card;
card = rank[ rand()%13 ];
card += " of ";
card += suit[ rand()%4 ];
if (verbose)
cout << card << "\n";
return card;
}
int main(int argc, char *argv[])
{
bool verbose = false;
int seedvalue = 0;
string stop_card = "Queen of Hearts";
for (int i=1; i<argc; i++) {
string option = argv[i];
if (option.compare(0,6,"-seed=") == 0) {
seedvalue = atoi(&argv[i][6]);
} else if (option.compare(0,6,"-stop=") == 0) {
stop_card = &argv[i][6];
} else if (option.compare("-verbose") == 0) {
verbose = true;
} else
cout << "option " << argv[i] << " ignored\n";
}
srand(seedvalue);
list deck[4];
while (1) {
string card = random_card(verbose);
char first[10];
char second[10];
sscanf(card.c_str(), "%s of %s", first,second);
// reverse engineer card suit and rank
int index2;
//suit index
for(int i=0; i<4; i++){
if(suit[i]==second){
index2=i;
break;
}
}
deck[index2].push_front(first);
if (card.compare(stop_card)==0){
break;
}
}
// print formatted table contents to stdout
cout << "Clubs : ";
cout << setw(3) << deck[0];
cout << endl;
cout << "Diamonds : ";
cout << setw(3) << deck[1];
cout << endl;
cout << "Hearts : ";
cout << setw(3) << deck[2];
cout << endl;
cout << "Spades : ";
cout << setw(3) << deck[3];
cout << endl;
}
The following are significant problems that will either hinder building (read: compile-time bugs) or actual runtime. This makes no claim these are all the bugs, but its certainly worth considering. I should note right off the top that the concept of a "sentinel" head-node allocation is almost- never needed in linked list management, and this code is not one of the exceptions. If the list is "empty" head should be null. If it isn't empty, head should not be null. Its just that simple, and this code would be leaps-and-bounds simpler if that were followed.
With that, read on.
Invalid Code:
list(int N=0, string current);
Reason: C++ requires all arguments following the first argument that is provided a default value to also have default values. This would be valid if N was the second parameter, or if current was also given a default value (or of course ,if neither had default values). All of the following are valid:
list(int N, string current);
list(int N, string current = "");
list(int N=0, string current = "");
As-written, it will fail to compile.
Invalid code: No matching constructor available
head = new node;
Reason: The structure node does not defined a default-compliant constructor (one that either has no parameters, or all parameters with default value provisions) but does specify a non-default constructor (one that requires at least one parameter). As a result, the language-supplied default constructor is not auto-generated and there is no node::node() constructor to be found.
Incorrect Code: Expression result is unused
void list::push_front(const string ¤t) {
head = new node;
head->next; // THIS LINE
}
Reason: This code blindly overwrites whatever is currently occupied in the head pointer with a new (invalid, see above for why) node allocation. Anything that was in head prior is leaked forever, and current is unused whatsoever. Fix this by allocating a new node with current as the value, settings its next pointer to head and head to the new node:
void list::push_front(const string ¤t)
{
node *p = new node(current);
p->next = head;
head = p;
}
Infinite Recursion
ostream& operator<<(ostream& out, const list& current)
{
out << current;
return out;
}
Reason: This code literally invokes itself. Recursively. Forever (well, until you run out of call-stack space).
NULL Pointer Dereference
inline node *list::findnode(int i)
{
if (i == -1)
return head;
node *p = head->next;
while (i--)
p = p->next;
return p;
}
Reason: This will walk the list uninhibited by validity checking for i iterations. Now imagine what this does on an empty list (in your case, that means head is non-null, but head->next is null) when passed anything besides -1: It will return NULL for i=0 and is outright undefined behavior for everything else.
NULL Pointer Dereference
void list::insert(int i, const string &din)
{
node *p = new node(din);
node *pp = findnode(i-1);
p->next = pp->next;
pp->next = p;
N++;
}
This assumes pp will never be null on return, and as we already discussed with the prior item, it most certainly can be when head is the sole node in your list, and is therefore "empty". This makes no attempt at checking pp for NULL prior to using it for dereferencing. This kid-gloves handling and the exceptions that have to be accounted for are directly related to maintaining a "sentinel" head node. The simplest way to fix it is to (a) Don't use sentinel nodes; use the universal sentinel value nullptr, and (b) check your return values before using them.
Ambiguous Reference: rank
card = rank[ rand()%13 ];
Reason: The standard library defines a special struct called std::rank used for determining the number of dimensions in a multi-dimension array. With the using namespace std; at the top of your code, the compiler is now forced to choose which one (the one in namespace std or the array you've defined prior to this code), and it cannot do so unequivocally. Thus it will not compile. Note: this is brought in by implicitly including <type_traits>, which is likely included by <string>, <iostream>, <iomanip> or any of a number of other nested includes. You can solve it a number of ways, including (but not limited to) a creative using clause, renaming the rank array to something that doesn't conflict, using a functional wrapper around a local static rank in the function etc.
Implicit conversion from signed to unsigned type (minor)
srand(seedvalue);
Reason: std::srand() takes an unsigned int parameter; you're passing a signed integer. Either static-cast to unsigned int or change the type of seedValue to unsigned int.
Invalid Code
list deck[4];
Reason: Class list does not have a default constructor. Recall the first item in this response. If you fix that, you will fix this as well.
And I didn't even run the code yet. I would strongly advise working on these issues, and give serious consideration to not using a "sentinel" node for your list head. Linked list code practically writes itself once you "know" a null head means the list is empty, a non-null head means it isn't.
I make no claims this is all the bugs. These were just ones I saw while reviewing the code, and all but one of them is significant.
EDIT Sample operator overload
Note: If you fix your linked list to use null as a head value when the list is empty (advised) this will need to change to simply start at head rather than head>next.
std::ostream& operator <<(std::ostream& os, const list& lst)
{
const node *p = lst.head ? lst.head->next : nullptr;
while (p)
{
os << p->data;
if ((p = p->next)) // note: assignment intentional
os << ',';
}
return os;
}
Related
I need help adjusting the createTree function.
Which accepts a string and after that character by character traverses it, creating a binary tree based on it
If it encounters the character 0, it recursively creates two sub-branches.
If it encounters another character, it saves it in the leaf node.
For the string in the example, I need to make a tree as in the picture, but the function does not work properly for me. Thank you in advance for your advice.
int x = 0;
Node* createTree(string str, int si, int ei)
{
if (si > ei)
return NULL;
Node *root = new Node((str[si] - '0'));
if(str[si] != '0')
{
x++;
root->m_Data = (str[si] - '0');
return root;
}
if(str[si]=='0')
{
x++;
root->m_Left = createTree(str,x,ei);
root->m_Right = createTree(str,x,ei);
}
return root;
}
int main ()
{
string str = "050067089";
Node *node = createTree(str,0,str.length());
printPreorder(node);
return 0;
}
The problem can quite easily be broken down into small steps (what you partly did in your question).
Start iterating at the first character
Create the root node
If the current character is non-zero, set the value of this node to this character
If current character is a zero, set this node to zero, create a left and a right node and get back to step 3 for every one of them. (That's the recursive part.)
Below is my implementation of this algorithm.
First, a little bit of setting up:
#include <iostream>
#include <string>
#include <memory>
struct Node;
// Iterator to a constant character, NOT a constant iterator
using StrConstIt = std::string::const_iterator;
using UniqueNode = std::unique_ptr<Node>;
struct Node
{
int value;
UniqueNode p_left;
UniqueNode p_right;
Node(int value)
: value(value) {}
Node(int value, UniqueNode p_left, UniqueNode p_right)
: value(value), p_left(std::move(p_left)), p_right(std::move(p_right)) {}
};
As you can see, I'm using std::unique_ptr for managing memory. This way, you don't have to worry about manually deallocating memory. Using smart pointers is often considered the more "modern" approach, and they should virtually always be preferred over raw pointers.
UniqueNode p_createNodeAndUpdateIterator(StrConstIt& it, StrConstIt stringEnd)
{
if (it >= stringEnd)
return nullptr;
UniqueNode node;
if (*it == '0')
// Create node with appropriate value
// Create branches and increment iterator
node = std::make_unique<Node>(
0,
p_createNodeAndUpdateIterator(++it, stringEnd),
p_createNodeAndUpdateIterator(it, stringEnd)
);
else
{
// Create leaf node with appropriate value
node = std::make_unique<Node>(*it - '0');
// Increment iterator
++it;
}
return node;
}
UniqueNode p_createTree(StrConstIt begin, StrConstIt end)
{
return p_createNodeAndUpdateIterator(begin, end);
}
The first function takes a reference to the iterator to the next character it should process. That is because you can't know how much characters a branch will have in its leaf nodes beforehand. Therefore, as the function's name suggests, it will update the iterator with the processing of each character.
I'm using iterators instead of a string and indices. They are clearer and easier to work with in my opinion — changing it back should be fairly easy anyway.
The second function is basically syntactic sugar: it is just there so that you don't have to pass an lvalue as the first argument.
You can then just call p_createTree with:
int main()
{
std::string str = "050067089";
UniqueNode p_root = p_createTree(str.begin(), str.end());
return 0;
}
I also wrote a function to print out the tree's nodes for debugging:
void printTree(const UniqueNode& p_root, int indentation = 0)
{
// Print the value of the node
for (int i(0); i < indentation; ++i)
std::cout << "| ";
std::cout << p_root->value << '\n';
// Do nothing more in case of a leaf node
if (!p_root->p_left.get() && !p_root->p_right.get())
;
// Otherwise, print a blank line for empty children
else
{
if (p_root->p_left.get())
printTree(p_root->p_left, indentation + 1);
else
std::cout << '\n';
if (p_root->p_right.get())
printTree(p_root->p_right, indentation + 1);
else
std::cout << '\n';
}
}
Assuming that the code which is not included in your question is correct, there is only one issue that could pose a problem if more than one tree is built. The problem is that x is a global variable which your functions change as a side-effect. But if that x is not reset before creating another tree, things will go wrong.
It is better to make x a local variable, and pass it by reference.
A minor thing: don't use NULL but nullptr.
Below your code with that change and the class definition included. I also include a printSideways function, which makes it easier to see that the tree has the expected shape:
#include <iostream>
using namespace std;
class Node {
public:
int m_Data;
Node* m_Left = nullptr;
Node* m_Right = nullptr;
Node(int v) : m_Data(v) {}
};
// Instead of si, accept x by reference:
Node* createTree(string str, int &x, int ei)
{
if (x >= ei)
return nullptr;
Node *root = new Node((str[x] - '0'));
if(str[x] != '0')
{
root->m_Data = (str[x] - '0');
x++;
return root;
}
if(str[x]=='0')
{
x++;
root->m_Left = createTree(str,x,ei);
root->m_Right = createTree(str,x,ei);
}
return root;
}
// Overload with a wrapper that defines x
Node* createTree(string str)
{
int x = 0;
return createTree(str, x, str.length());
}
// Utility function to visualise the tree with the root at the left
void printSideways(Node *node, string tab) {
if (node == nullptr) return;
printSideways(node->m_Right, tab + " ");
cout << tab << node->m_Data << "\n";
printSideways(node->m_Left, tab + " ");
}
// Wrapper for above function
void printSideways(Node *node) {
printSideways(node, "");
}
int main ()
{
string str = "050067089";
Node *node = createTree(str);
printSideways(node);
return 0;
}
So, as you see, nothing much was altered. Just si was replaced with x, which is passed around by reference, and x is defined locally in a wrapper function.
Here is the output:
9
0
8
0
7
0
6
0
5
I am doing programming project 6 of chapter 13 of Problem Solving With C++ Global Edition by Savitch. I am trying to overload the << operator to print the list. It works up to the point where my walker pointer is assigned to the value of the head pointer for the 2nd time, making it impossible for me to use the list as a circularly linked list which is the project requirement. I've recreated the most basic form of the source of the crash here:
Suitors.h file:
#include <iostream>
struct Suitor {
int number;
Suitor *link;
};
class Suitors {
private:
Suitor *head=nullptr;
int size=0;
public:
//Constructors
Suitors(int sizePar);
~Suitors(); //Destructor
//Print list
friend std::ostream& operator<<(std::ostream& outs, const Suitors& list);
};
Suitors.cpp File:
#include "Suitors.h"
Suitors::Suitors(int sizePar) {
Suitor *tempPtr = new Suitor;
size = sizePar;
for (int i=0; i<size; i++) {
if (head==nullptr) {
head = tempPtr;
}
tempPtr->number = i+1;
if (i==size-1) {
tempPtr->link = head;
}
else {
tempPtr->link = new Suitor;
}
tempPtr = tempPtr->link;
}
}
Suitors::~Suitors() {
Suitor *walker1 = head, *walker2 = head;
for (int i=0; i<size; i++) {
walker1 = walker1->link;
delete walker2;
walker2 = walker1;
}
head = nullptr;
}
std::ostream& operator<<(std::ostream& outs, const Suitors& list) {
Suitor *walker = list.head;
walker = walker->link;
walker = list.head;
/*
for (int i=0; i<list.size; i++) {
outs << walker->number << " ";
walker = walker->link;
}
*/
}
main.cpp file:
#include <iostream>
#include "Suitors.h"
void project6();
int main() {
std::cout << "Hello, World!" << std::endl;
project6();
return 0;
}
void project6() {
Suitors six(6);
std::cout << six << std::endl;
}
I've trimmed down the << operator overloading to just what creates the error. walker is set to the list head, then advanced a node, then set back to the list head, causing the error. The desired behavior is to be able to set walker to the head node more than once.
When I compiled your code, the compiler told me two things.
There is no return statement in the function std::ostream& operator<<(std::ostream&, const Suitors&), which is declared as returning non-void.
The outs parameter to that function is unused.
Even though compilers lack human intelligence, they sure are good at spotting oversights in code. So I addressed those warnings by adding the line return outs; to that function. The segmentation fault went away.
Always enable and address compiler warnings!
Singly Linked List and Node classes and the start of the main function, where I wrote a brief outline of the code functionality. The issue is toward the end of the main function. I wrote '...' in place of what I believe to be irrelevant code because it simply parses strings and assigns them to the string temp_hold[3] array.
#include <bits/stdc++.h>
using namespace std;
class Node {
public:
string value;
string attr;
string tagname;
Node *next;
Node(string c_tagname, string c_attr, string c_value) {
this->attr = c_attr;
this->value = c_value;
this->tagname = c_tagname;
this->next = nullptr;
}
};
class SinglyLinkedList {
public:
Node *head;
Node *tail;
SinglyLinkedList() {
this->head = nullptr;
this->tail = nullptr;
}
void insert_node(string c_tagname, string c_attr,string c_value) {
Node *node = new Node(c_tagname,c_attr, c_value);
if (!this->head) {
this->head = node;
} else {
this->tail->next = node;
}
this->tail = node;
}
};
int main(int argc, char **argv) {
/* storage is a vector holding pointers to the linked lists
linked lists are created and the linked list iterator sll_itr is incremented when
previous line begins with '</' and the currentline begins with '<'
linked lists have nodes, which have strings corresponding to tagname, value, and attribute
*/
SinglyLinkedList *llist = new SinglyLinkedList();
vector<SinglyLinkedList*> sllVect;
sllVect.push_back(llist);
auto sll_itr = sllVect.begin();
string temp_hold[3];
// to determine new sll creation
bool prev = false;
bool now = false;
//input
int num1, num2;
cin >> num1; cin >> num2;
//read input in
for (int i = 0; i <= num1; ++i) {
string line1, test1;
getline(cin, line1);
test1 = line1.substr(line1.find("<") + 1);
//determine to create a new linked list or wait
if (test1[0] == '/') {
prev = now;
now = true;
} else {
//make a node for the data and add to current linked list
if (i > 0) {
prev = now;
now = false;
//if last statement starts with '</' and current statment starts with '<'
// then start a new sll and increment pointer to vector<SinglyLinkedList*>
if (prev && !now) {
SinglyLinkedList *llisttemp = new SinglyLinkedList();
sllVect.push_back(llisttemp);
sll_itr++;
}
}
//parse strings from line
int j = 0;
vector<string> datastr;
vector<char> data;
char test = test1[j];
while (test) {
if (isspace(test) || test == '>') {
string temp_for_vect(data.begin(),data.end());
if (!temp_for_vect.empty()) {
datastr.push_back(temp_for_vect);
}
data.clear();
} else
if (!isalnum(test)) {
} else {
data.push_back(test);
}
j++;
test = test1[j];
}
//each node has 3 strings to fill
int count = 0;
for (auto itrs = datastr.begin(); itrs!=datastr.end(); ++itrs) {
switch (count) {
case 0:
temp_hold[count]=(*itrs);
break;
case 1:
temp_hold[count]=(*itrs);
break;
case 2:
temp_hold[count]=(*itrs);
break;
default:
break;
}
count++;
}
}
cout << "before storing node" << endl;
(*sll_itr)->insert_node(temp_hold[0], temp_hold[1], temp_hold[2]);
cout << "after" << endl;
}
cout << "AFTER ELSE" << endl;
return 0;
}
And here is the line that breaks the code. The auto sll_itr is dereferenced which means *sll_itr is now a SinglyLinkedList* and we can call the insert_node(string, string, string) to add a node to the current linked list. However when I keep the line, anything after the else statement brace does not run, which means the cout<<"AFTER ELSE"<< endl; does not fire. If I remove the insert_node line, then the program runs the cout<<"AFTER ELSE"<< endl; I am unsure what the issue is.
(*sll_itr)->insert_node(temp_hold[0],temp_hold[1],temp_hold[2]);
cout << "after" << endl;
} //NOT HANGING. This closes an else statement.
cout << "AFTER ELSE" << endl;
return 0;
}
Compiled as g++ -o myll mylinkedlist.cpp and then myll.exe < input.txt And input.txt contains
8 3
<tag1 value = "HelloWorld">
<tag2 name = "Name2">
</tag2>
</tag1>
<tag5 name = "Name5">
</tag5>
<tag6 name = "Name6">
</tag6>
Your linked list isn't the problem, at least not the problem here.
A recipe for disaster in the making: retaining, referencing, and potentially manipulating, an iterator on a dynamic collection that potentially invalidates iterators on container-modification. Your code does just that. tossing out all the cruft between:
vector<SinglyLinkedList*> sllVect;
sllVect.push_back(llist);
auto sll_itr = sllVect.begin();
....
SinglyLinkedList *llisttemp = new SinglyLinkedList();
sllVect.push_back(llisttemp); // HERE: INVALIDATES sll_iter on internal resize
sll_itr++; // HERE: NO LONGER GUARANTEED VALID; operator++ CAN INVOKE UB
To address this, you have two choices:
Use a container that doesn't invalidate iterators on push_back. There are really only two sequence containers that fit that description: std::forward_list and std::list.
Alter your algorithm to reference by index`, not by iterator. I.e. man your loop to iterate until the indexed element reaches end-of-container, then break.
An excellent discussion about containers that do/do-not invalidate pointers and iterators can be found here. It's worth a read.
I'm getting a segfault when I run this code and I'm not sure why. Commenting out a particular line (marked below) removes the segfault, which led me to believe that the recursive use of the iterator "i" may have been causing trouble, but even after changing it to a pointer I get a segfault.
void executeCommands(string inputstream, linklist<linklist<transform> > trsMetastack)
{
int * i=new int;
(*i) = 0;
while((*i)<inputstream.length())
{
string command = getCommand((*i),inputstream);
string cmd = getArguments(command,0);
//cout << getArguments(command,0) << " " << endl;
if (cmd=="translate")
{
transform trs;
trs.type=1;
trs.arguments[0]=getValue(getArguments(command,2));
trs.arguments[1]=getValue(getArguments(command,3));
((trsMetastack.top)->value).push(trs);
executeCommands(getArguments(command,1),trsMetastack);
}
if (cmd=="group")
{
//make a NEW TRANSFORMS STACK, set CURRENT stack to that one
linklist<transform> transformStack;
trsMetastack.push(transformStack);
//cout << "|" << getAllArguments(command) << "|" << endl;
executeCommands(getAllArguments(command),trsMetastack); // COMMENTING THIS LINE OUT removes the segfault
}
if (cmd=="line")
{ //POP transforms off of the whole stack/metastack conglomeration and apply them.
while ((trsMetastack.isEmpty())==0)
{
while ((((trsMetastack.top)->value).isEmpty())==0) //this pops a single _stack_ in the metastack
{ transform tBA = ((trsMetastack.top)->value).pop();
cout << tBA.type << tBA.arguments[0] << tBA.arguments[1];
}
trsMetastack.pop();
}
}
"Metastack" is a linked list of linked lists that I have to send to the function during recursion, declared as such:
linklist<transform> transformStack;
linklist<linklist<transform> > trsMetastack;
trsMetastack.push(transformStack);
executeCommands(stdinstring,trsMetastack);
The "Getallarguments" function is just meant to extract a majority of a string given it, like so:
string getAllArguments(string expr) // Gets the whole string of arguments
{
expr = expr.replace(0,1," ");
int space = expr.find_first_of(" ",1);
return expr.substr(space+1,expr.length()-space-1);
}
And here is the linked list class definition.
template <class dataclass>
struct linkm {
dataclass value; //transform object, point object, string... you name it
linkm *next;
};
template <class dataclass>
class linklist
{
public:
linklist()
{top = NULL;}
~linklist()
{}
void push(dataclass num)
{
cout << "pushed";
linkm<dataclass> *temp = new linkm<dataclass>;
temp->value = num;
temp->next = top;
top = temp;
}
dataclass pop()
{
cout << "pop"<< endl;
//if (top == NULL) {return dataclass obj;}
linkm<dataclass> * temp;
temp = top;
dataclass value;
value = temp->value;
top = temp->next;
delete temp;
return value;
}
bool isEmpty()
{
if (top == NULL)
return 1;
return 0;
}
// private:
linkm<dataclass> *top;
};
Thanks for taking the time to read this. I know the problem is vague but I just spent the last hour trying to debug this with gdb, I honestly dunno what it could be.
It could be anything, but my wild guess is, ironically: stack overflow.
You might want to try passing your data structures around as references, e.g.:
void executeCommands(string &inputstream, linklist<linklist<transform> > &trsMetastack)
But as Vlad has pointed out, you might want to get familiar with gdb.
Last week, we created a program that manages sets of strings, using classes and vectors. I was able to complete this 100%. This week, we have to replace the vector we used to store strings in our class with simple singly linked lists.
The function basically allows users to declare sets of strings that are empty, and sets with only one element. In the main file, there is a vector whose elements are a struct that contain setName and strSet (class).
HERE IS MY PROBLEM: It deals with the copy constructor of the class. When I remove/comment out the copy constructor, I can declare as many empty or single sets as I want, and output their values without a problem. But I know I will obviously need the copy constructor for when I implement the rest of the program. When I leave the copy constructor in, I can declare one set, either single or empty, and output its value. But if I declare a 2nd set, and i try to output either of the first two sets, i get a Segmentation Fault. Moreover, if i try to declare more then 2 sets, I get a Segmentation Fault. Any help would be appreciated!!
Here is my code for a very basic implementation of everything:
Here is the setcalc.cpp: (main file)
#include <iostream>
#include <cctype>
#include <cstring>
#include <string>
#include "strset2.h"
using namespace std;
// Declares of structure to hold all the sets defined
struct setsOfStr {
string nameOfSet;
strSet stringSet;
};
// Checks if the set name inputted is unique
bool isSetNameUnique( vector<setsOfStr> strSetArr, string setName) {
for(unsigned int i = 0; i < strSetArr.size(); i++) {
if( strSetArr[i].nameOfSet == setName ) {
return false;
}
}
return true;
}
int main() {
char commandChoice;
// Declares a vector with our declared structure as the type
vector<setsOfStr> strSetVec;
string setName;
string singleEle;
// Sets a loop that will constantly ask for a command until 'q' is typed
while (1) {
cin >> commandChoice;
// declaring a set to be empty
if(commandChoice == 'd') {
cin >> setName;
// Check that the set name inputted is unique
if (isSetNameUnique(strSetVec, setName) == true) {
strSet emptyStrSet;
setsOfStr set1;
set1.nameOfSet = setName;
set1.stringSet = emptyStrSet;
strSetVec.push_back(set1);
}
else {
cerr << "ERROR: Re-declaration of set '" << setName << "'\n";
}
}
// declaring a set to be a singleton
else if(commandChoice == 's') {
cin >> setName;
cin >> singleEle;
// Check that the set name inputted is unique
if (isSetNameUnique(strSetVec, setName) == true) {
strSet singleStrSet(singleEle);
setsOfStr set2;
set2.nameOfSet = setName;
set2.stringSet = singleStrSet;
strSetVec.push_back(set2);
}
else {
cerr << "ERROR: Re-declaration of set '" << setName << "'\n";
}
}
// using the output function
else if(commandChoice == 'o') {
cin >> setName;
if(isSetNameUnique(strSetVec, setName) == false) {
// loop through until the set name is matched and call output on its strSet
for(unsigned int k = 0; k < strSetVec.size(); k++) {
if( strSetVec[k].nameOfSet == setName ) {
(strSetVec[k].stringSet).output();
}
}
}
else {
cerr << "ERROR: No such set '" << setName << "'\n";
}
}
// quitting
else if(commandChoice == 'q') {
break;
}
else {
cerr << "ERROR: Ignoring bad command: '" << commandChoice << "'\n";
}
}
return 0;
}
Here is the strSet2.h:
#ifndef _STRSET_
#define _STRSET_
#include <iostream>
#include <vector>
#include <string>
struct node {
std::string s1;
node * next;
};
class strSet {
private:
node * first;
public:
strSet (); // Create empty set
strSet (std::string s); // Create singleton set
strSet (const strSet ©); // Copy constructor
// will implement destructor and overloaded assignment operator later
void output() const;
}; // End of strSet class
#endif // _STRSET_
And here is the strSet2.cpp (implementation of class)
#include <iostream>
#include <vector>
#include <string>
#include "strset2.h"
using namespace std;
strSet::strSet() {
first = NULL;
}
strSet::strSet(string s) {
node *temp;
temp = new node;
temp->s1 = s;
temp->next = NULL;
first = temp;
}
strSet::strSet(const strSet& copy) {
cout << "copy-cst\n";
node *n = copy.first;
node *prev = NULL;
while (n) {
node *newNode = new node;
newNode->s1 = n->s1;
newNode->next = NULL;
if (prev) {
prev->next = newNode;
}
else {
first = newNode;
}
prev = newNode;
n = n->next;
}
}
void strSet::output() const {
if(first == NULL) {
cout << "Empty set\n";
}
else {
node *temp;
temp = first;
while(1) {
cout << temp->s1 << endl;
if(temp->next == NULL) break;
temp = temp->next;
}
}
}
The C++ standard states that types used in a standard container (such as std::vector) must be copy constructable AND assignable.
Since you have not implemented a custom assignment operator on the class strSet, the compiler will generate one for you that does a simple memberwise copy. In your case, this means the 'first' pointer will be copied directly. Obviously this means two objects now 'own' the nodes in the set, and you will get a crash when it is freed twice.
Some tips:
Implement a custom assignment operator that does the same thing as your copy constructor
Read up on passing objects by reference, and by const reference where possible. You are doing a lot of unnecessary copying of your containers and strings when you pass by value.
e.g.
bool isSetNameUnique(const vector& strSetArr, const string& setName)
Good luck :)
this looks a bit peculiar:
strSet::strSet(string s) {
node *temp;
temp = new node;
temp->s1 = s;
temp->next = NULL;
first = temp;
}
what if 'first' is pointing to something already? You are then effectively killing the previous list and causing a mem leak.
Your strSet copy constructor does not assign the member first when its argument is empty. This causes Undefined Behavior.
Also, the strSet assignment operator (operator=) that was shown before the edit was definitely wrong; and it really is not a good idea to define a copy constructor but allow the destructor and assignment operator to be implicitly defined by the compiler. See the Rule of Three.
One common way to implement the Big Three when they need to do considerable management (like in this case) looks something like:
class strSet {
private:
void cleanup();
void create_from(const node* n);
// ...
};
strSet::~strSet() { cleanup(); }
strSet::strSet(const strSet& copy) : first(NULL) { create_from(copy.first); }
strSet& strSet::operator=(const strSet& rtSide) {
if (this != &rtSide) {
cleanup(); // trash old contents of *this
create_from(rtSide.first); // clone contents of rtSide
}
return *this;
}