MongoDB C++ BSONObj lifetimes - c++

When using the C++ client library and retain an BSONObj object returned from a cursor->next(), will a subsequent new query (using the same connection) somehow destroy that object ? My understanding is that its a smart pointer and will survive until the last reference to it goes out of scope. Practice however shows me that a subsequent query seemingly destroys the object. I get a segmentation fault when I access hasField/getField methods of the object after the new query is traversed.
Following is the relevant code:
BSONObj DB::getmsg
(
const string &oid
)
{
BSONObj query = BSON("_id" << OID(oid));
auto_ptr<DBClientCursor> cursor = c.query("nsdb.messages", query);
if (cursor->more())
return cursor->next();
throw Exception("could not find object %s", oid.c_str());
}
void DB::getchildren
(
const string &oid,
vector<BSONObj> &children
)
{
BSONObj query = BSON("parent" << oid);
BSONObj fields = BSON("_id" << 1);
auto_ptr<DBClientCursor> cursor =
c.query("nsdb.messages", query, 0, 0, &fields);
while (cursor->more())
children.push_back(cursor->next());
}
void DB::message
(
const string &oid,
string &str,
bool body
)
{
connect();
BSONObjBuilder response;
try
{
BSONObj n = getmsg(oid);
response.append("message", n);
vector<BSONObj> children;
getchildren(oid, children);
if (children.size() > 0)
{
BSONArrayBuilder a;
vector<BSONObj>::iterator i;
for (i = children.begin(); i != children.end(); i++)
{
if (!(*i).isEmpty() && (*i).hasField("_id"))
{
string child = (*i).getField("_id").__oid().str();
a.append("http://127.0.0.1:12356/lts/message/" + child);
}
}
if (a.len() > 0)
response.append("children", a.done());
}
if (body)
{
string fname;
if (n.hasField("fname"))
fname = n.getField("fname").str();
if (fname.empty())
{
throw Exception("no fname field in message record %s",
n.jsonString().c_str());
}
auto_mmapR<const void *, int> m(fname);
response.appendBinData("body", m.size(), BinDataGeneral,
m.get());
}
str = response.obj().jsonString();
}
catch (const DBException &x)
{
throw Exception("%s", x.what());
}
}
If I move the part:
string fname;
if (n.hasField("fname"))
fname = n.getField("fname").str();
to a spot before getchildren(...) is called, then the fname field is correctly retrieved from the message otherwise the exception is thrown 'no fname field in message record'

Okay, I did not understood too much of your code, but short answer:
- The data returned by the cursor is invalidated at the next iteration or when the cursor is destroyed.
Use the "getOwned()" to have a copy of the BSONObj object:
cursor->next().getOwned();

Related

Std::bad_alloc thrown in the middle of While Loop

I'm writing a function that handles an order input file (csv) using a while loops to iterate though it.
762212,1,2020-03-15,10951-3,64612-2,57544-1,80145-1,27515-2,16736-1,79758-2,29286-2,51822-3,39096-1,32641-3,63725-3,64007-2,23022-1,16974-3,26860-2,75536-2,26461-1
1,373975319551257,12-2023
258572,2,2020-03-15,96497-1,70616-1,80237-2,22248-2,56107-1,59695-1,37948-3,21316-3,63498-1,18329-1,56833-1,66295-1,47680-3,30346-1
1,201741963232463,02-2022
857003,3,2020-03-15,16655-1,88019-3,75069-3,96017-2,46883-2,15138-1,77316-1,70063-3,54452-3,86429-2,15134-2,60176-1,12946-3
2,cfeeham3s
747893,4,2020-03-17,48520-1,93268-2,63636-1,23750-2,99771-3,83203-1,21316-3,89921-2,15134-3,82831-1,30346-2,54044-3,28561-1,14792-2,23523-3,56826-2
1,3571379825697064,04-2025
Every two lines represents an input. I have the following function that handles this input:
list<Order> orders;
void read_orders(string file_name) {
fstream read_file;
read_file.open(file_name, ios::in);
if (read_file.is_open()) {
string s;
int line_num = 1; // keeps track of line number in input file
int o_id;
string o_date;
int c_id;
vector<LineItem> itms;
while (getline(read_file, s)) {
cout << orders.size(); // shows that only two objects are added before failure
if (line_num % 2 == 1) { // handle odd numbered lines of input
auto data = split(s, ',');
int o_id = stoi(data[0]);
string o_date = data[1];
int c_id = stoi(data[2]);
vector<LineItem> itms;
// get line items
int n_line_items = data.size() - 3;
vector<string> end_data(data.end() - n_line_items, data.end());
for (string x: end_data) {
auto parts = split(x, '-');
LineItem* it = new LineItem(stoi(parts[0]), stoi(parts[1]));
itms.push_back(*it);
delete it;
}
} else { // handle even numbered lines of input
auto data = split(s, ',');
Credit* pay_credit = new Credit(0.0, data[1], data[2]); // initialize each type of payment
PayPal* pay_paypal = new PayPal(0.0, data[1]);
WireTransfer* pay_wire = new WireTransfer(0.0, data[1], data[2]);
if (data[0] == "1") {
Order* ordr = new Order(o_id, o_date, c_id, itms, *pay_credit);
orders.push_back(*ordr);
delete ordr;
} else if (data[0] == "2") {
Order* orr = new Order(o_id, o_date, c_id, itms, *pay_paypal);
orders.push_back(*orr);
delete orr;
} else if (data[0] == "3") {
Order* odr = new Order(o_id, o_date, c_id, itms, *pay_wire);
orders.push_back(*odr);
delete odr;
}
delete pay_credit; // trying to clean up memory
delete pay_paypal;
delete pay_wire;
}
line_num += 1;
}
read_file.close();
}
}
Because of my cout statement, I can tell that it only adds two items to the list before running into the std::bad_alloc error. It seems to happen when it switches from adding a Credit object to adding a PayPal object into the Order(...) when it's initialized. I did a lot of research into why this might happen, so I tried to clean up as much as I knew how to (I'm new to C++) but the same error kept popping up. Does the error happen when I'm adding things to the list or is it when I'm creating these new objects?/How could I fix something like that?
Here are my class definitions in case that's important:
class Payment {
public:
double amount;
string print_detail() {
return "hey";
};
};
class Credit: public Payment {
private:
string card_number;
string expiration;
public:
Credit(double amt, string cn, string exp) {
this->amount = amt;
this->card_number = cn;
this->expiration = exp;
}
string print_detail() {
return "Credit card " + this->card_number + ", exp. " + this->expiration;
}
};
class PayPal: public Payment {
private:
string paypal_id;
public:
PayPal(double amt, string pp_id) {
this->amount = amt;
this->paypal_id = pp_id;
}
virtual string print_detail() {
return "Paypal ID: " + this->paypal_id;
}
};
class WireTransfer: public Payment {
private:
string bank_id;
string account_id;
public:
WireTransfer(double amt, string b_id, string a_id) {
this->amount = amt;
this->bank_id = b_id;
this->account_id = a_id;
}
string print_detail() {
return "Wire transfer from Bank ID " + this->bank_id + ", Account# " + this->account_id;
}
};
class LineItem {
private:
int item_id;
int qty;
public:
LineItem(int i_id, int qt) {
this->item_id = i_id;
this->qty = qt;
}
double subtotal() {
double subtot = 0.0;
for (auto x: items) {
if (x.item_id == this->item_id) {
subtot += x.price * this->qty;
}
}
return subtot;
};
};
class Order {
private:
int order_id;
string order_date;
int cust_id;
vector<LineItem> line_items;
Payment payment;
public:
Order(int o_id, string o_date, int c_id, vector<LineItem> li, Payment pay) {
this->order_id = o_id;
this->order_date = o_date;
this->cust_id = c_id;
this->line_items = li;
this->payment = pay;
}
string pay_type = "";
double total() {
double result = 0.0;
for (auto li: line_items) {
result += li.subtotal();
}
return result;
}
string print_order() {
string text = "===========================\nOrder #";
text += to_string(this->order_id) + ", Date: " + this->order_date + "\nAmount: $";
text += to_string(this->total()) + ", Paid by ";
text += payment.print_detail();
return text;
}
};
And this was the error message showing that it did insert two items:
001122terminate called after throwing an instance of 'std::bad_alloc'
what(): std::bad_alloc
Process returned 3 (0x3)
std::bad_alloc is often thrown when there is not enough memory to be allocated. I can't say if this will solve the problem, but your repeated allocations and deallocations of objects are both unnecessary and harmful (causing memory fragmentation).
Instead of
LineItem* it = new LineItem(stoi(parts[0]), stoi(parts[1]));
itms.push_back(*it);
delete it;
you should do
itms.push_back(LineItem(stoi(parts[0]), stoi(parts[1]));
or
itms.emplace_back(stoi(parts[0]), stoi(parts[1]));
The same applies to every occurence of new in read_orders. You don't need any of them.
Another helpful thing you can do is to preallocate memory for std::vector. If you don't know how many items it will have, do an educated guess (100, 1000, 10000, etc.).
itms.reserve(1000); //before you start to push_back() to it
Also, make sure to std::move your vectors if you want to transfer the whole content of it and not make a copy.

Print a string from a pointer to its member class

So I'm trying to print a string, but I get no output. However the other values in the class prints just fine.
In main I have a for loop that prints the the values for the Skill class. In Skill I have a pointer to the Ability class.
class Skill {
private:
Ability* ability;
public:
Ability* GetAbility() {
return ability;
};
}
It gets assigned in the constructor like this:
Skill::Skill(Ability* ability){
this->ability = ability;
}
The Ability class contains just a Name and a score.
class Ability {
private:
string name;
float score;
public:
Ability(string name, float score) {
this->name = name;
this->score = score;
};
string Name() { return name; }
float GetScore() { return score; }
};
Now in main I create a few skills and assign an ability to it. as is a container class that initializes a few ablities in a vector and I can get an ability based on its name.
Skill s* = new Skill[2]
s[0] = Skill(&as.GetAbility("Strength"));
s[1] = Skill(&as.GetAbility("Charisma"));
And then we print
cout << s[i].GetAbility()->Name() << " " << s[i].GetAbility()->GetScore();
However the only output I get is the score. No name what so ever and I can't figure it out. I've tried a few things, but still noting is printing. I'm sure I'm missing something simple that will make me facepalm, but in my defense I haven't written C++ in over 10 years. Thanks in advance.
EDIT: as.GetAbility looks like this:
Ability AbilityScores::GetAbility(string abilityName) {
for (int i = 0; i < abilityScores.size(); i++) {
if (abilityScores[i].Name() == abilityName) {
return abilityScores[i];
}
}
return Ability();
}
abilityScores is a vector
Your AbilityScores::GetAbility() method is returning an Ability object by value, which means it returns a copy of the source Ability, and so your Skill objects will end up holding dangling pointers to temporary Ability objects that have been destroyed immediately after the Skill constructor exits. So your code has undefined behavior.
AbilityScores::GetAbility() needs to return the Ability object by reference instead:
Ability& AbilityScores::GetAbility(string abilityName) {
for (int i = 0; i < abilityScores.size(); i++) {
if (abilityScores[i].Name() == abilityName) {
return abilityScores[i];
}
}
throw ...; // there is nothing to return!
}
...
Skill s* = new Skill[2];
s[0] = Skill(&as.GetAbility("Strength"));
s[1] = Skill(&as.GetAbility("Charisma"));
...
If you want to return a default Ability when the abilityName is not found, consider using std::map instead of std::vector:
private:
std::map<std::string, Ability> abilityScores;
AbilityScores::AbilityScores() {
abilityScores["Strength"] = Ability("Strength", ...);
abilityScores["Charisma"] = Ability("Charisma", ...);
...
}
Ability& AbilityScores::GetAbility(string abilityName) {
// if you don't mind Name() returning "" for unknown abilities...
return abilityScores[abilityName];
// otherwise...
auto iter = abilityScores.find(abilityName);
if (iter == abilityScores.end()) {
iter = abilityScores.emplace(abilityName, 0.0f).first;
}
return iter->second;
}
...
Skill s* = new Skill[2];
s[0] = Skill(&as.GetAbility("Strength"));
s[1] = Skill(&as.GetAbility("Charisma"));
...
Otherwise, return the Ability object by pointer instead:
Ability* AbilityScores::GetAbility(string abilityName) {
for (int i = 0; i < abilityScores.size(); i++) {
if (abilityScores[i].Name() == abilityName) {
return &abilityScores[i];
}
}
return nullptr;
// or:
abilityScores.emplace_back(abilityName, 0.0f);
return &(abilityScores.back());
}
...
Skill s* = new Skill[2];
s[0] = Skill(as.GetAbility("Strength"));
s[1] = Skill(as.GetAbility("Charisma"));
...

Changing a private value of a class in method not returning the changes to main()

I'm having a problem that I haven't found an answer for in a week now. I have a dynamic array class and it has a method to add string values to it. It's supposed to represent an inventory you can add items to. However, I find that the changes made in the method to the private values of the class element aren't "updated" when I try to call for a print-method for the class element "backpack" later in the main(). I think this might be a problem due to referencing issues, but I've seen this work when a class hasn't been in a different module.
My "backpack" module print and add methods:
const int INITIAL_SIZE = 5;
Inventory::Inventory():
array_(new string[INITIAL_SIZE]),
max_space_(INITIAL_SIZE),
used_space_(0) {}
void Inventory::add(string item){
if ( size() == max_space_ ) {
string* new_array = new string[2 * max_space_];
for ( int i = 0; i < size(); ++i ) {
new_array[i] = array_[i];
}
delete [] array_;
array_ = new_array;
max_space_ = 2 * max_space_;
}
array_[used_space_] = item;
++used_space_;
}
void Inventory::print() {
for ( int i = 0; i < size(); ++i ) {
cout << array_[i] << endl;
}
}
The main():
Inventory inv;
string input;
while (cout << "input> "
and getline(cin,input)){
add_to_bag(input,inv);
So the point is you reset the inventory when you give it new contents. The function add_to_bag(); is:
void add_to_bag(string input, Inventory inv){
const string WHITESPACE1_REGEX = "[[:space:]]*";
const string WHITESPACE2_REGEX = "[[:space:]]+";
const string WORD_REGEX = "[[:alpha:]_]+";
const string LINE_REGEX =
WHITESPACE1_REGEX +
WORD_REGEX +
"(" +
WHITESPACE2_REGEX +
WORD_REGEX +
")*" +
WHITESPACE1_REGEX;
regex line_reg(LINE_REGEX);
regex word_regex(WORD_REGEX);
string line = input;
if ( regex_match(line, line_reg) ) {
sregex_iterator iter(line.begin(), line.end(), word_regex);
sregex_iterator end;
while ( iter != end ) {
inv.add(iter->str());
++iter;
}
} else {
cout << "Error: unknown inventory contents." << endl;
}
}
Your problem is:
void add_to_bag(string input, Inventory inv);
You pass a copy of the Inventory object to add_to_bag. You modify that copy ... and then it gets thrown away. The fix is to pass by reference:
void add_to_bag(string input, Inventory &inv);
Incidentally, in real-life code, I would strongly advise the use of std::vector<std::string> rather than "roll your own". There are a number of tricky exception handling issues you have got wrong here - and unless Inventory doesn't have a destructor (implying a memory leak), or does have a correct copy constructor I would have expected you to run into "double free" issues. (Read about "The Rule of Three".)
A simple way to design your class would be as follows:
class Inventory {
private:
std::vector<std::string> items_;
public:
Inventory(){}
~Inventory(){}
void addItem( const std::string& item ) {
items_.push_back( item );
}
void printInventory() const {
int idx = 0;
for (; idx < items_.size(); ++idx ) {
std::cout << items_[idx] << std::endl;
}
}
void clearInventory() {
items_.clear();
}
};
And as for your problem Martin Bonner had already answered it with the modifying of the copy and the removal of it afterwards and the other issues with the memory management.

Update an integer in an ADO RecordSet in C++

I am trying to update a record in an ADO RecordSet. The recordset is loaded from a XML file, that has previously been saved to disc via rs->Save.
When trying to assign an integer value, I get a _com_error Exception:
HRESULT = -2147217887
Description = Fehler bei einem aus mehreren Schritten bestehenden Vorgang. Prüfen Sie die einzelnen Statuswerte.
Message = IDispatch error #3105
Source = Microsoft Cursor Engine
The connections Error collection yields the same message and no additional details.
I cannot figure out why the assignment does not work. What would be the correct way to assign the value to the field in question?
The variant returned from the Recordset via GetValue() is of the type VT_I4.
It seems to be a conversion issue however. The field won't accept the type of variant it gets assigned. However v = _variant_t(1l, VT_I4); did not work either.
_RecordsetPtr DataService::LoadRecordsetFromXML(string fileName)
{
//get a recordset
_RecordsetPtr rs;
try {
HRESULT hr = rs.CreateInstance(__uuidof(Recordset));
if(hr)
return nullptr;
hr = rs->Open(fileName.c_str(),"Provider=MSPersist;",adOpenDynamic,adLockBatchOptimistic,adCmdFile);
if(hr)
return nullptr;
return rs;
}
catch (_com_error &ce)
{
ShowComErrorMessageBox(ce, rs);
}
catch(...)
{
AfxMessageBox("An unknown error has occured.");
}
return nullptr;
}
bool DataService::AddMemoToXML(Memo m, string fileName)
{
_RecordsetPtr rs = LoadRecordsetFromXML(fileName);
if(!rs)
{
return false;
}
std::stringstream fstream;
fstream << "MEMO_ID = " << m.OldId;
try {
rs->Filter = fstream.str().c_str();
HRESULT hr;
if(rs->adoEOF)
{
hr = rs->AddNew();
}
//set values
{
FieldPtr field;
_bstr_t bstring;
_variant_t v;
field = rs->Fields->GetItem("MEMO_ID");
v = _variant_t(m.Id);
DataTypeEnum t = field->GetType(); //adInteger
field->PutValue(v); //Exception here
field->Value = v; //Exception here
field->Value = m.Id; //Exception here
field = rs->Fields->GetItem("MEMO_TEXT");
bstring = m.Text.c_str();
field->Value = bstring; //works fine
}
rs->Update();
rs->Filter = "";
bool ret = SaveRecordsetToXML(rs, fileName);
rs->Close();
return ret;
}
catch (_com_error &ce)
{
ShowComErrorMessageBox(ce, rs);
}
catch(...)
{
AfxMessageBox("An unknown error has occured.");
}
return false;
}
*edit: The memo class for reference:
class Memo
{
public:
Memo(void);
virtual ~Memo(void);
int Id;
int OldId;
wstring Text;
int Person;
wstring Firma;
int OrgId;
int JobId;
wstring PCode;
int UserId;
int RolleId;
wstring Kat;
COleDateTime ContactDate;
wstring Knoten;
wstring CodeV;
wstring CodeR;
wstring Eint;
wstring Stichwort;
};
Further testing on different fields yielded that the conversion like the following works.
field = rs->Fields->GetItem("PERSON");
v = 10l;
field->Value = v;
and that v = _variant_t(1l, VT_I4); is fine, too.
The error has to be related to the properties of the field MEMO_ID and is probably raised due to the fact, that it is an AutoId column.
An integer without a cast to long might use the wrong constructor of the variant, interpreting the number as memory location.

std::list iterator

The following code instead of returning a pointer back to an audioResource it returns
something else which is invalid, i've gone through with a debugger and the problem is with this line
return *list_it;
Here is my function:
AudioResource* AudioManager::getResource(const unsigned int ID)
{
std::list<AudioResource*>::iterator list_it;
for(list_it = m_resources.begin(); list_it!= m_resources.end(); list_it++)
{
if((*list_it)->getID()==ID)
{
std::cout<<*(*list_it)->getFileName();
return *list_it;
}
}
return nullptr;
}
O and I have tried putting it as (*list_it) but i got the same results =s
How it is populated...
Resource* AudioManager::addResource(const unsigned int ID,
const std::string fileName, const unsigned int scope,
const std::string type)
{
AudioResource* temp;
if(type == "AUDIO_TYPE_SAMPLE")
{
temp = new AudioResource(ID,fileName,scope,
RESOURCE_AUDIO,AUDIO_TYPE_SAMPLE);
m_resources.push_back(temp);
}
else if(type == "AUDIO_TYPE_STREAM")
{
temp = new AudioResource(ID,fileName,scope,
RESOURCE_AUDIO,AUDIO_TYPE_STREAM);
m_resources.push_back(temp);
}
return temp;
}
call to get resource
cout<<AudioManager::getInstance()->getResource(IDnum)->getFileName();
If type is neither of the two values an uninitialized pointer is added to m_resources:
AudioResource* temp;
if(type == "AUDIO_TYPE_SAMPLE")
{
temp = new AudioResource(ID,fileName,scope,RESOURCE_AUDIO,AUDIO_TYPE_SAMPLE);
}
else if(type == "AUDIO_TYPE_STREAM")
{
temp = new AudioResource(ID,fileName,scope,RESOURCE_AUDIO,AUDIO_TYPE_STREAM);
}
m_resources.push_back(temp);
Initialize temp to NULL and only add to m_resources if temp != NULL.
Also, the function returns the same uninitialized pointer.
You return nullptr in case the ID doesn't exist, but you never check against it at the call site, which will give you a null pointer access if the ID doesn't exist and which will likely create problems.
AudioManager::getInstance()->getResource(IDnum)->getFileName();
Change that to
AudioResource* res = AudioManager::getInstance()->getResource(IDnum);
if(res)
std::cout << res->getFileName();