Тип libxml2 xmlNodePtr изменяется, когда я наблюдаю это

Я провел некоторое время вчера, отлаживая прекрасный Гейзенбаг, где xmlNodePtrТип изменился бы на меня. Этот пример показывает ошибку:

#include <iostream>

#include <vector>
#include <string>
#include <memory>    // std::unique_ptr

#include <cstdint>

#include <libxml/tree.h>
#include <libxml/parser.h>

struct SomeDataType {
std::vector<std::vector<std::string>> data;

explicit SomeDataType(uint32_t rows_, uint32_t columns_)
: data(rows_)
{
for (uint32_t row = 0; row < rows_; ++row) {
data[row].resize(columns_);
}
}
};

static std::vector<xmlNodePtr> GetChildren(xmlNodePtr node)
{
std::vector<xmlNodePtr> children;

xmlNodePtr child = node->children;
while (child) {
if (child->type == XML_ELEMENT_NODE) {
children.push_back(child);
}
child = child->next;
}

return children;
}

int main() {
std::unique_ptr<xmlDoc, void(*)(xmlDoc*)> document = { xmlParseEntity("libxml2-fail.xml"), xmlFreeDoc };

SomeDataType{ 3, 2 };

xmlNodePtr root = xmlDocGetRootElement(document.get());

for (const xmlNodePtr &child : GetChildren(root)) {
const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...
std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
std::cout << entry->name << std::endl;
}
}

Составлено с:

g++ -g -std=c++14 -Wall -Wextra -pedantic -I/usr/include/libxml2 libxml2-fail.cpp -lxml2 -o fail.out

XML-файл:

<?xml version="1.0" encoding="utf-8"?>
<data>
<tag>
<subtag>1</subtag>
</tag>
</data>

Запуск дает мне следующий вывод:

Expected 1 but was 17

Проходя через GDB, все в порядке, пока мы не достигнем линии const xmlNodePtr & = ..., Вместо того, чтобы иметь тип XML_ELEMENT_NODEимеет тип XML_ENTITY_DECL, Однако, если я запускаю следующие команды, ссылка xmlNodePtr превращается в тип, который я ожидаю:

48          const xmlNodePtr &entry = GetChildren(child)[0];
(gdb) n
49          std::cout << "Expected " << XML_ELEMENT_NODE << " but was " << entry->type << std::endl;
(gdb) p *entry
$1 = {_private = 0x0, type = XML_ENTITY_DECL, name = 0x0, children = 0xb7e67d7c <std::string::_Rep::_S_empty_rep_storage+12>, last = 0x0, parent = 0x69, next = 0x0, prev = 0x9, doc = 0x0, ns = 0x805edb8, content = 0x805edb8 "", properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 60648, extra = 2053}
(gdb) p *child
$2 = {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ee98 "tag", children = 0x805eea8, last = 0x805ef98, parent = 0x805edb8, next = 0x805efe8, prev = 0x805ee08, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 3, extra = 0}
(gdb) p GetChildren(child)
$3 = std::vector of length 1, capacity 1 = {0x805eef8}
(gdb) p *entry
$4 = {_private = 0x0, type = XML_ELEMENT_NODE, name = 0x805ef38 "subtag", children = 0x805ef48, last = 0x805ef48, parent = 0x805ee58, next = 0x805ef98, prev = 0x805eea8, doc = 0x805ece8, ns = 0x0, content = 0x0, properties = 0x0, nsDef = 0x0, psvi = 0x0, line = 4, extra = 0}
(gdb)

У меня нет проблемы, когда я вместо этого зацикливаюсь на одном элементе так:

for (const xmlNodePtr &entry : GetChildren(child)) {
...
}

У меня также нет проблемы, когда я не делаю xmlNodePtr константная ссылка вот так:

xmlNodePtr entry = GetChildren(child)[0];

Однако согласно этот вопрос, это не должно быть проблемой.

SomeDataType структура странно необходима; в противном случае я получаю Segfault, потому что entry становится нулевым указателем.

Откуда эта ошибка?

1

Решение

Когда вы делаете это:

const xmlNodePtr &entry = GetChildren(child)[0]; // Problem here...

Вы фактически связываете ссылку с временным способом, который не продлевается. operator[] возвращает ссылку, поэтому вы не привязываете ссылку к временному объекту — вы привязываете ссылку к ссылке. Но это вернулось от operator[] относится к элементу в основе временный vector вернулся GetChildren() который выходит из области видимости в конце строки, оставляя себя висячей ссылкой.


Однако, когда вы вместо этого попробовали:

for (const xmlNodePtr &entry : GetChildren(child)) {

это синтаксический сахар для:

{
auto&& __range = GetChildren(child); // bind temporary to reference
// lifetime IS extended
auto b = begin(__range);
auto e = end(__range);
for (; b != e; ++b) {
const xmlNodePtr& entry = *b;
// ...
}
}

Вот, *b не является временным или какой-либо частью временного — это ссылка на контейнер, срок службы которого длится до __range делает, что через все тело цикла. Нет свисающих ссылок.


Так же,

xmlNodePtr entry = GetChildren(child)[0];

просто копирование, никаких проблем с ссылками вообще.

3

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]