BSONObj 在容器中的存储:无法获取字段

Storage of BSONObj in container: can't get the fields

我使用 mongodb c++ 驱动程序从数据库 mydb、集合 mycollection 获取一些记录。 当我使用 mongo::BSONObj current_obj=cursor->next(); 获取记录,然后尝试使用 getFieldgetFieldDotted 获取某些字段 field1 (字段存在于记录中)时,它总是成功(并打印"got the field"- 请参阅下面的代码)。我还检查了 BSONElement returned 的内容:它们很好。

然后我将所有找到的记录作为 BSONObj 推回一个向量(QVectorboost::container::vector),然后 return 来自查询的向量。

稍后,在其他函数中,我从该向量迭代 BSONObj。在这里调用 getFieldgetFieldDotted 会以某种概率抛出异常:例如,40% 的程序运行没有发生异常(打印出 "got the field from vector" 10 次),60% 的程序运行我得到Caught BSONElement: bad type 88(最后的 int 值可能因当前 BSONObj 而不同:可能是 -120110126 或其他。但是,它保留在每条记录上)。

为什么会这样?

代码:

boost::container::vector<mongo::BSONObj> makeQuery(QString query)
{
    std::auto_ptr <mongo::DBClientCursor> cursor = my_connection->query("mydb.mycollection", mongo::fromjson(query.toStdString()));
    //...(checking getLastError())

    //QVector<mongo::BSONObj> bsonobjects;
    boost::container::vector<mongo::BSONObj> bsonobjects;

    while (cursor->more())
    {
        mongo::BSONObj current_obj=cursor->next();
        for (int ii=0; ii<10; ii++)
        {
            current_obj.getField("field1");
            std::cout<<"---got the field---\n";
        }
        bsonobjects.push_back(current_obj);
    }
    return bsonobjects;
}

void processBSONObjVector()
{
    //....

    //(below we call makeQuery)
    try
    {
        //QVector<mongo::BSONObj> objects=makeQuery(query_string);
        boost::container::vector<mongo::BSONObj> objects=makeQuery(query_string);
        for (int i=0; i<objects.size; i++)
                for (int ii=0; ii<10; ii++)
                {
                    objects[i].getField("field1");
                    std::cout<<"---got the field from vector---\n";
                }
    }
    catch (const mongo::DBException &e)
    {
        std::cout << "Caught " << e.what() << std::endl;
    }
}

好像出现了heisenbug,有记忆的东西:如果(幸好)BSONObj没有被其他数据改写,可以取field1的值。 BSONObj 向量中的内容可能已损坏,因为它脱离了上下文并且没有留下任何智能指针。

BSONObj 不能用 BSONObj(const BSONObj & other) 复制吗?

文档建议按值传递 BSONObj - 当我使用 push_back(object) 时就是这种情况,不是吗?


此外,也许无关紧要,当我打开连接时有一个有问题的代码(不确定执行 mongo::DBClientBase*dynamic_cast<mongo::DBClientConnection*> 是否是内存安全的,DBClientBase 是抽象的):

my_connection 是到 mongodb 的已建立连接:

//... (make a connection)
mongo::client::initialize(); 
//...
my_connection = dynamic_cast<mongo::DBClientConnection*> (connection_string.connect(errmsg)); //connection_string is valid mongo::ConnectionString
....

That link对答案有影响。

对于 c++-0x (#if __cplusplus >= 201103L),BSONObj 具有移动构造函数和显式创建的复制构造函数:

BSONObj(const BSONObj&) = default;

对于低于c++0x的c++版本,也会创建默认的copy ctor。

但是 BSONObj 对象包含指针 (!!!) 和内部对象(缓冲区): const char* _objdata; SharedBuffer _ownedBuffer; SharedBuffer 还包含没有复制构造函数的指针 boost::intrusive_ptr _holder(默认值除外)。

所以复制的不是内容,而是指针。

legacy-1.0.5 BSONObj.h:

     BSONObj& operator=(BSONObj otherCopy) {
        this->swap(otherCopy);
        return *this;
    }

    /** Swap this BSONObj with 'other' */
    void swap(BSONObj& other) {
        using std::swap;
        swap(_objdata, other._objdata);
        swap(_ownedBuffer, other._ownedBuffer);
    }

同样,指针被交换。

所以,我们不能用copy ctor或operator=复制BSONObj

它应该在文档中正确反映,like for BSONObjBuilder,如果它是有意的。

两种解决方案:

1) 在 makeQuery() 和 return 中的 BSONObj 上创建智能指针(更好)。

2) 在 makeQuery() 上下文中使用 BSONObj(更糟糕的是:它可能仅用于短期且不重复的工作 BSONObj,否则会导致代码不可读和非可重用性)。

EDIT1:创建智能指针并return使用它会导致相同的错误答案the code comments:

OWNED CASE

If the BSONObj owns the buffer, the buffer can be shared among several BSONObj's (by assignment). In this case the buffer is basically implemented as a shared_ptr.

UNOWNED CASE

A BSONObj can also point to BSON data in some other data structure it does not "own" or free later. For example, in a memory mapped file. In this case, it is important the original data stays in scope for as long as the BSONObj is in use. If you think the original data may go out of scope, call BSONObj::getOwned() to promote your BSONObj to having its own copy. On a BSONObj assignment, if the source is unowned, both the source and dest will have unowned pointers to the original buffer after the assignment. If you are not sure about ownership but need the buffer to last as long as the BSONObj, call getOwned().

copy() 方法与 getOwned() 一样有效,提供 BSONObj 的拥有副本。 Return of owned copy 结果没有错误。

mongo::BSONObj* curr_elem=new mongo::BSONObj();
(*curr_elem)=cursor->next().getOwned(); //or: next().copy();