I am farily new to c++ and have already read some topics about storing pointers to objects or the objects themselves in a vector.
I decided to store the objects in the vector, because I do not push_back many objects at runtime, the vector is just created once and leaved like this.
My problem now is, that I have another object that gets a vector as argument and searches for a certain object in the passed vector. If it finds this object, it stores a pointer to it, if not, the variable is set to NULL.
Eventhough I do not push_back any items, the pointer seems to point to a wrong location in other functions.
The object that searches for the element in the vector has a public function in which the pointer should be returned. It would be very slow if I search for the object at every function call, so this should not be an option.
Are there other solutions or do I have to switch to a vector of pointers?
Some code snippets:
Constructor of the object that searches the vector:
MySearch::MySearch(QVector<Obj> objVector)
:objVector(objVector) {
found = NULL
foreach(Obj o, this->objVector) {
if(..found..) {
found = &o;
break;
}
}
}
Getter function:
Obj* MySearch::getObject() {
return found;
}
The problem is because the variable o is local and will be out of scope as soon as the loop ends. If you take the address of the vector element instead of the o, it will works.
#include <iostream>
#include <vector>
#include <string>
using namespace std;
class MySearch
{
public:
MySearch(const vector<string> &items)
: items_(items)
{
// Skipping validation
found_ = &(items_[5]);
}
string *getObject() {return found_;}
private:
vector<string> items_;
string *found_;
};
int main()
{
string str = "test#";
vector<string> aux;
for (int i = 0; i < 10; ++i)
aux.push_back(str + (char)('0' + i)); // test#0 ... test#9
MySearch ms(aux);
cout << *(ms.getObject()) << endl; // test#5
return 0;
}
foreach(Obj o, this->objVector) {
if(..found..) {
found = &o;
break;
}
} // life time of o ends here.
o resides on stack and it's life-time is limited to the loop only. Having reference to it and later returning causes undefined behavior.
If you were to use BOOST_FOREACH (from the boost C++ libraries), then you could use a non-const reference to the objects in the vector. Q_FOREACH does not support non-const references:
BOOST_FOREACH(Obj& o, this->objVector) {
if(..found..) {
found = &o;
break;
}
}
Alternatively use iterators and a for loop.
Related
I'm trying to delete an item from a vector with erase() function but I keep getting an error. I
searched everywhere but can't find an answer
#include <iostream>
#include <vector>
#include <map>
#include <iterator>
#include <algorithm>
using namespace std;
class Person{
private:
string name;
public:
void set_name(string name){
this->name = name;
}
string get_name(){
return name;
}
class Record{
private:
vector <Person> book;
public:
void delete_person(string name){
for(Person p : book){
if(book.get_name() == name){
book.erase(p);
}
}
}
};
int main(){
// nothing in main yet
return 0;
}
I get en error in the delete_person() function in the record class: No matching member function for call to 'erase'
void delete_person(string name){
for(Person p : book){
if(book.get_name() == name){
book.erase(p);
}
}
}
fails for several reasons.
std::vector::erase does not accept items, it accepts iterators, locations of items to be removed.
Range-based for loops are very simple and limited in their abilities. They go from start to finish and are extremely intolerant of changes to the container while iterating. If you add or remove an item while iterating it, the hidden bookkeeping used by the loop becomes invalid and the loop breaks. And not the nice break sort of breaking. They tend to take the whole program down with them.
In Person p : book p is a new object that is a copy of an item in book. It's not the original or a reference to the original in the container. C++ defaults to values instead of references in almost every case. Unless you specifically request otherwise, you pass by value, return by value, and iterate by value.
Instead, employ the Erase-Remove Idiom. Here is an example with added commentary where I saw it fitting or educational.
#include <iostream>
#include <vector>
#include <map>
#include <iterator>
#include <algorithm>
using namespace std;
class Person
{
private:
string name;
public:
Person(const std::string & name) // added for testing
: name(name) // this is a member initializer list In C++ all class members
// and base classes must be initialized before the program can
// enter the body of the constructor. This trick allows us to
// initialize members rather than initializing them to their
// defaults (if the type has a default) and then setting them
// inside the body and wasting time doing two things where one
// thing was required
{
}
void set_name(string name) // side note consider saving construction of a new
// string and accepting name by const reference rather
// than by value and potentially making a copy.
// void set_name(const string & name)
// const because we do not intend to change `name`
// and because the compiler can take advantage of the
// promise not to change it in many interesting ways.
{
this->name = name;
}
string get_name() const // const because getters generally should not change the
// object this allows us to keep the class "const-correct"
// side note consider saving construction of a new
// string and returning by const reference rather than
// by value and making a copy.
// const string & get_name() const
{
return name;
}
};
class Record
{
private:
vector<Person> book;
public:
void add_person(const std::string & name) // added for testing
{
book.emplace_back(name);
}
void delete_person(string name) // again consider passing name by const reference
{
book.erase(std::remove_if(book.begin(), // from start of list
book.end(), // to the end
[name](const Person &p)
{
return p.get_name() == name;
}), // moves all items to be removed to the end of the
// list, then returns start of range to erase
book.end()); // erase to the end of the list
// Why erase separately? Because remove functions don't actually remove. They
// move the unwanted values to the end of the list. Looks silly, but much easier
// and safer to write. For example, this won't change the size of the list and
// break loops that count on the size to remain the same.
}
friend std::ostream & operator<<(std::ostream & out,
const Record & rec) // added for testing
{
for (const auto & item: rec.book) // print all items in book
// const because printing should not change
// the printed
// auto to let the compiler figure out the type
// & because we don't want to make a copy
{
out << item.get_name() << '\n';
}
return out;
}
};
int main()
{
Record r;
r.add_person("Bill");
r.add_person("Ted");
r.add_person("Rufus");
std::cout << r << std::endl;
r.delete_person("Ted");
std::cout << r << std::endl; // Ted should now be gone from the list
return 0;
}
Expected output:
Bill
Ted
Rufus
Bill
Rufus
book.erase(p);
book is a vector. The parameter to a vector's erase() method is an iterator.
for(Person p : book){
p is the value in the vector, and actually it is a copy of the value in the vector. You cannot pass a value to erase(). You must pass an iterator as a parameter. Passing some random copy of some random value in a vector to its erase() method is not going to accomplish anything useful.
std::vector has begin() and end() methods that return the iterator to the beginning and the end of a sequence that defines the contents of the vector.
This may be used with various algorithms, like std::find_if or std::remove_if, together with std::vector::erase to effect the removal of a value or multiple values from your vector.
I have created a member variable (in the class Group) that is a shared_ptr to a vector (called members that stores objects of type User (a different class). Here is the member variable definition (empty):
std::shared_ptr <std::vector<User>> members = std::make_shared<std::vector<User>>();
To eliminate a certain User object from the vector (members), I created a member function (remove) inside the class Group. It checks all the User objects inside the vector and, when encounters the one with the a.URI value that is looking for, it erases the object at that location in the vector.
void Group::remove(User a) {
for (auto i = 0; i < members->size(); i++){
if (a.URI == (*(members->begin()+i)).URI) members->erase(*(members->begin() + i));
}
That is my remove function by now, but it doesn't work. I have checked different configurations but can`t make it work. Is there a problem with my function? How should this function be implemented (special rules for shared_ptr?)? Is there a better/optimal way?
I think no more code is needed here to solve my question. However, for those who want a MCV example, I tried to write one here (Ideone).
Firstly, std::vector::erase expects an iterator as its argument, so change
members->erase(*(members->begin() + i))
to
members->erase(members->begin() + i)
Secondly, after the element being erased, when i++ is evaluated, you will skip the next element or get out of the bound of the vector. You might change the loop to
for (auto i = 0; i < members->size(); ) {
if (a.URI == (*(members->begin()+i)).URI)
members->erase(members->begin() + i);
else
i++;
}
Why don't you declare the vector members as static member of the group class. I did it like this:
#include <bits/stdc++.h>
using namespace std;
class User{
public:
int URI;
User(int a){
URI = a;
}
};
class Group {
public:
static vector<User> members;
void remove(User a) {
for (int i = 0; i < members.size();){
if (a.URI == (*(members.begin()+i)).URI) members.erase((members.begin() + i));
else
i++;
}
}
Group(User a, User b, User c){
members.push_back(a);
members.push_back(b);
members.push_back(c);
}
};
vector<User> Group::members;
int main() {
User a(1), b(2), c(3);
Group obj(a, b, c);
obj.remove(a);
return 0;
}
Hope it helps.
How do I get the position of an element inside a vector, where the elements are classes. Is there a way of doing this?
Example code:
class Object
{
public:
void Destroy()
{
// run some code to get remove self from vector
}
}
In main.cpp:
std::vector<Object> objects;
objects.push_back( <some instances of Object> );
// Some more code pushing back some more stuff
int n = 20;
objects.at(n).Destroy(); // Assuming I pushed back 20 items or more
So I guess I want to be able to write a method or something which is a member of the class which will return the location of itself inside the vector... Is this possible?
EDIT:
Due to confusion, I should explain better.
void Destroy(std::vector<Object>& container){
container.erase( ?...? );
}
The problem is, how can I find the number to do the erasing...? Apparently this isn't possible... I thought it might not be...
You can use std::find to find elements in vector (providing you implement a comparison operator (==) for Object. However, 2 big concerns:
If you need to find elements in a container then you will ger much better performance with using an ordered container such as std::map or std::set (find operations in O(log(N)) vs O(N)
Object should not be the one responsible of removing itself from the container. Object shouldn't know or be concerned with where it is, as that breaks encapsulation. Instead, the owner of the container should concern itself ith such tasks.
The object can erase itself thusly:
void Destroy(std::vector<Object>& container);
{
container.erase(container.begin() + (this - &container[0]));
}
This will work as you expect, but it strikes me as exceptionally bad design. Members should not have knowledge of their containers. They should exist (from their own perspective) in an unidentifiable limbo. Creation and destruction should be left to their creator.
Objects in a vector don't automatically know where they are in the vector.
You could supply each object with that information, but much easier: remove the object from the vector. Its destructor is then run automatically.
Then the objects can be used also in other containers.
Example:
#include <algorithm>
#include <iostream>
#include <vector>
class object_t
{
private:
int id_;
public:
int id() const { return id_; }
~object_t() {}
explicit object_t( int const id ): id_( id ) {}
};
int main()
{
using namespace std;
vector<object_t> objects;
for( int i = 0; i <= 33; ++i )
{
objects.emplace_back( i );
}
int const n = 20;
objects.erase( objects.begin() + n );
for( auto const& o : objects )
{
cout << o.id() << ' ';
}
cout << endl;
}
If you need to destroy the n'th item in a vector then the easiest way is to get an iterator from the beginning using std::begin() and call std::advance() to advance how ever many places you want, so something like:
std::vector<Object> objects;
const size_t n = 20;
auto erase_iter = std::advance(std::begin(objects), n);
objects.erase(erase_iter);
If you want to find the index of an item in a vector then use std::find to get the iterator and call std::distance from the beginning.
So something like:
Object object_to_find;
std::vector<Object> objects;
auto object_iter = std::find(std::begin(objects), std::end(objects), object_to_find);
const size_t n = std::distance(std::begin(objects), object_iter);
This does mean that you need to implement an equality operator for your object. Or you could try something like:
auto object_iter = std::find(std::begin(objects), std::end(objects),
[&object_to_find](const Object& object) -> bool { return &object_to_find == &object; });
Although for this to work the object_to_find needs to be the one from the actual list as it is just comparing addresses.
I have a pretty standard class with some public member functions and private variables.
My problem originally stems from not being able to dynamically name object instances of my class so I created an array of pointers of the class type:
static CShape* shapeDB[dbSize];
I have some prompts to get info for the fields to be passed to the constructor (this seems to work):
shapeDB[CShape::openSlot] = new CShape(iParam1,sParam1,sParam2);
openSlot increments properly so if I were to create another CShape object, it would have the next pointer pointing to it. This next bit of code doesn't work and crashes consistently:
cout << shapeDB[2]->getName() << " has a surface area of: " << shapeDB[2]->getSA() << shapeDB[2]->getUnits() << endl;
The array of pointers is declared globally outside of main and the get() functions are public within the class returning strings or integers. I'm not sure what I'm doing wrong but something relating to the pointer set up I'm sure. I'm writing this code to try and learn more about classes/pointers and have gotten seriously stumped as I can't find anyone else trying to do this.
I'm also curious as to what the CShape new instances get named..? if there is any other way to dynamically create object instances and track the names so as to be able to access them for member functions, I'm all ears.
I've tried all sorts of permutations of pointer referencing/de-referencing but most are unable to compile. I can post larger chunks or all of the code if anyone thinks that will help.
class CShape {
int dim[maxFaces];
int faces;
string units;
string type;
string name;
bool initialized;
int slot;
public:
static int openSlot;
CShape();
CShape(int, string, string); // faces, units, name
~CShape();
void initialize(void);
// external assist functions
int getA(void) {
return 0;
}
int getSA(void) {
int tempSA = 0;
// initialize if not
if(initialized == false) {
initialize();
}
// if initialized, calculate SA
if(initialized == true) {
for(int i = 0; i < faces; i++)
{
tempSA += dim[i];
}
return(tempSA);
}
return 0;
}
string getUnits(void) {
return(units);
}
string getName(void) {
return(name);
}
// friend functions
friend int printDetails(string);
};
// constructor with values
CShape::CShape(int f, string u, string n) {
initialized = false;
faces = f;
units = u;
name = n;
slot = openSlot;
openSlot++;
}
My guess is you use the CShape constructor to increment CShape::openSlot?
You're probably changing the value before it's read, thus the pointer is stored in a different location.
Try replacing openSlot with a fixed value to rule out this CShape::option.
-- code was added --
I'm pretty sure this is the problem, the constructor is executed before the asignment, which means the lhs. will be evaluated after CShape::openSlot is incremented.
Below I have written a sample program that I have written to learn about passing a list of objects to another class. I talk about the problems I am having below.
#include <iostream>
#include <vector>
using namespace std;
class Integer_Class
{
int var;
public:
Integer_Class(const int& varin) : var(varin) {}
int get_var() { return var; }
};
class Contains_List
{
typedef Integer_Class* Integer_Class_Star;
Integer_Class_Star list;
public:
Contains_List(const Integer_Class_Star& listin) : list(listin) {}
Integer_Class* get_list() { return list; }
};
int main (int argc, char * const argv[])
{
// Create a vector to contain a list of integers.
vector<Integer_Class> list;
for(int i = 0; i < 10; i++)
{
Integer_Class temp_int(i);
list.push_back(temp_int);
}
This is where the errors start occuring. Could someone please look at the second class definition and the code below and shed some light on what I'm doing wrong. Thank you so much, as always!
// Import this list as an object into another object.
Contains_List final(list);
// Output the elements of the list by accessing it through the secondary object.
for(int i = 0; i < 10; i++)
{
cout << final.get_list()[i].get_var();
}
return 0;
}
You don't mention what sort of errors you are getting, but one very obvious problem with your code is that the constructor for Contains_List expects a pointer to Integer_Class while the parameter you are sending it (list) is of type vector<Integer_Class>.
A vector is not the same as an array, so you cannot pass it as pointer to the type it contains. Either change your constructor to accept a vector or pointer/reference to vector, or change the code that is causing you problems so that it sends it a pointer to an array.
The 'Contains_List' constructor takes in an 'Integer_Class*'
You declare 'list' to be of type 'vector', yet you pass it to the the 'Contians_List' constructor. You should change the 'Contains_List' class so that it holds a vector instead of an Integer_List array. The two are not interchangeable.
You could also change the vector to be an array of Integer_List's instead, if you so wished.