Reputation: 313
I have been trying to implement some convert structs for yaml-cpp for my own data types. Of which one is a class, not just a struct. The encode function works fine. But the decode function doesn't. I try to get a string from a yaml file and set the correct variable in the class.
template<>
struct convert<EngineNode*> {
static Node encode(EngineNode *rhs) {
Node node;
std::string type;
if(rhs->type == 0) {
type = "node";
} else if(rhs->type == 1) {
type = "scene";
} else if(rhs->type == 3) {
type = "particle";
}
node[type]["name"] = rhs->name;
node[type]["type"] = rhs->type;
node[type]["velocity"] = rhs->getVelocity();
node[type]["position"] = rhs->getPosition();
node[type]["rotation"] = rhs->getRotation();
for(unsigned i = 0; i < rhs->children.size(); i++) {
if(rhs->children[i]->type == SPRITE) {
node[type]["children"].push_back((SpriteNode*)rhs->children[i]);
} else {
node[type]["children"].push_back(rhs->children[i]);
}
}
return node;
}
static bool decode(const Node& node, EngineNode *rhs) {
if((!node["root"]["node"].IsDefined()) && (!node["root"]["scene"].IsDefined()) && (!node["particle"].IsDefined())) {
return false;
}
std::string type;
if(node["root"]["node"].IsDefined()) {
type = "node";
} else if(node["root"]["scene"].IsDefined()) {
type = "scene";
}
const Node n = node["root"][type];
rhs->name = n["name"].as<std::string>();
rhs->type = n["type"].as<int>();
rhs->setVelocity(n["velocity"].as<Velocity>());
rhs->setPosition(n["position"].as<Point>());
rhs->setRotation(n["rotation"].as<float>());
for(unsigned i = 0; i < n["children"].size(); i++) {
if(n["children"]["type"].as<int>() == SPRITE) {
rhs->addChild(n["children"].as<SpriteNode*>());
} else {
rhs->addChild(n["children"].as<EngineNode*>());
}
}
return true;
}
};
If you want to look at the full source its at github.
The problem is that it segfaults with the following error:
Program received signal SIGSEGV, Segmentation fault.
0x00007ffff73e3b5c in std::string::assign(std::string const&) () from /usr/lib/libstdc++.so.6
(gdb) bt
#0 0x00007ffff73e3b5c in std::string::assign(std::string const&) () from /usr/lib/libstdc++.so.6
#1 0x000000000040f590 in YAML::convert<EngineNode*>::decode (node=..., rhs=0x8) at src/yaml_config.cpp:90
#2 0x000000000040ef9f in YAML::as_if<EngineNode*, void>::operator() (this=0x7fffffffe4c0) at /usr/include/yaml-cpp/node/impl.h:119
#3 0x0000000000407a7d in YAML::Node::as<EngineNode*> (this=0x7fffffffe5c8) at /usr/include/yaml-cpp/node/impl.h:143
#4 0x00000000004074f9 in YamlConfig::readNode (this=0x8cf810, yaml_node=...) at src/yaml_config.cpp:172
#5 0x000000000040739a in YamlConfig::read (this=0x8cf810, path=...) at src/yaml_config.cpp:161
#6 0x0000000000404fa5 in GameScene::GameScene (this=0x8d62d0) at game/gamescene.cpp:16
#7 0x0000000000404205 in Main::initGameScene (this=0x61d2b0) at src/main.cpp:75
#8 0x0000000000404480 in main () at src/main.cpp:145
I can't figure out why that happens, it's probably not related to yaml-cpp at all but just my very limited understanding of templates and other stuff that's used here. (I only know very basic c++)
Any help would be appreciated,
Peter
Upvotes: 1
Views: 4934
Reputation: 7437
The problem is that the Node::as<T>
function allocates a variable of type T
on the stack and then uses copy semantics to return the converted value. In the above case, T
is EngineNode *
, which is a pointer to an EngineNode
. The as
function does not allocate additional memory for an actual EngineNode
, so dereferencing (using *
or ->
) results in a segmentation fault.
The fix would be to rework your conversion functions to use references instead of pointers:
template<>
struct convert<EngineNode> {
static Node encode(const EngineNode &rhs) {
Node node;
std::string type;
if(rhs.type == 0) {
type = "node";
} else if(rhs.type == 1) {
type = "scene";
} else if(rhs.type == 3) {
type = "particle";
}
// ...
}
static bool decode(const Node& node, EngineNode &rhs) {
if((!node["root"]["node"].IsDefined()) && (!node["root"]["scene"].IsDefined()) && (!node["particle"].IsDefined())) {
return false;
}
std::string type;
if(node["root"]["node"].IsDefined()) {
type = "node";
} else if(node["root"]["scene"].IsDefined()) {
type = "scene";
}
const Node n = node["root"][type];
rhs.name = n["name"].as<std::string>();
// ...
}
};
Make sure that you have defined an appropriate copy-constructor for your EngineNode
class:
class EngineNode {
public:
EngineNode();
EngineNode(const EngineNode &rhs); // Copy-constructor
~EngineNode();
// ...
};
That way, when your EngineNode
is assigned in YamlConfig::read
, it will make a valid copy if re-written like this:
EngineNode *YamlConfig::read(std::string path) {
YAML::Node doc = YAML::LoadFile(path);
EngineNode *node = new EngineNode(doc.as<EngineNode>());
// ...
return node;
}
Anyone calling this function should be aware that this allocates a new EngineNode
object on the heap. This object will probably need to be deallocated at some point by calling delete
on it. Alternatively, you could rework your API to use smart pointers:
std::shared_ptr<EngineNode> YamlConfig::read(std::string path) {
YAML::Node doc = YAML::LoadFile(path);
auto node = std::make_shared(doc.as<EngineNode>());
// ...
return node;
}
Upvotes: 1