I was able to safely call builder, but builder2 exits with a segment fault. The compiler does not output any warnings. I would like to know the cause of the segment fault. This code is a builder pattern to compose html. ul and li are collected with emplace_back and finally str() is called to build the parts and return them as string.
#include <memory>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct HtmlBuilder;
struct HtmlElement {
string name;
string text;
vector<HtmlElement> elements;
const size_t indent_size = 2;
HtmlElement() {}
HtmlElement(const string& name, const string& text)
: name(name), text(text) {}
string str(int indent = 0) const {
ostringstream oss;
string i(indent_size * indent, ' ');
oss << i << "<" << name << ">" << endl;
if (text.size() > 0)
oss << string(indent_size * (indent 1), ' ') << text << endl;
for (const auto& e : elements) oss << e.str(indent 1);
oss << i << "</" << name << ">" << endl;
return oss.str();
}
static unique_ptr<HtmlBuilder> build(string root_name) {
return make_unique<HtmlBuilder>(root_name);
}
};
struct HtmlBuilder {
HtmlBuilder(string root_name) { root.name = root_name; }
// void to start with
HtmlBuilder& add_child(string child_name, string child_text) {
HtmlElement e{child_name, child_text};
root.elements.emplace_back(e);
return *this;
}
// pointer based
HtmlBuilder* add_child_2(string child_name, string child_text) {
HtmlElement e{child_name, child_text};
root.elements.emplace_back(e);
return this;
}
string str() { return root.str(); }
operator HtmlElement() const { return root; }
HtmlElement root;
};
int main() {
// easier
HtmlBuilder builder{"ul"};
builder.add_child("li", "hello").add_child("li", "world");
cout << builder.str() << endl;
auto* builder2 = HtmlElement::build("ul")
->add_child_2("li", "hello")
->add_child_2("li", "world");
cout << (*builder2).str() << endl;
return 0;
}
The output results are as follows
- hello
- world
Segmentation fault (core dumped)
CodePudding user response:
You dynamically create a std::unique_ptr
object holding the builder with HtmlElement::build("ul")
. This std::unique_ptr
object gets destroyed at the end of the full expression which means the object builder2
points to is destroyed and dereferencing it is undefined behaviour resulting in the crash you observed.
I recommend not returning a dynamically allocated builder object at all. Instead move the definition of the HtmlElement::build
below the definition of HtmlBuilder
. You may also want to consider allowing for move semantics to avoid creating unnecessary copies of the objects:
struct HtmlElement {
...
static HtmlBuilder build(string root_name);
};
struct HtmlBuilder {
HtmlBuilder(string root_name) { root.name = std::move(root_name); }
// void to start with
HtmlBuilder& add_child(string child_name, string child_text) &
{
HtmlElement e{ child_name, child_text };
root.elements.emplace_back(e);
return *this;
}
HtmlBuilder&& add_child(string child_name, string child_text) &&
{
HtmlElement e{ child_name, child_text };
root.elements.emplace_back(e);
return std::move(*this);
}
string str() { return root.str(); }
operator HtmlElement() &&
{
return std::move(root);
}
HtmlElement root;
};
inline HtmlBuilder HtmlElement::build(string root_name) {
return { root_name };
}
int main() {
HtmlBuilder builder{ "ul" };
builder.add_child("li", "hello").add_child("li", "world");
cout << builder.str() << endl;
auto builder2 = HtmlElement::build("ul")
.add_child("li", "hello")
.add_child("li", "world");
cout << builder2.str() << endl;
HtmlElement product = std::move(builder2); // use move constructor for creating product here (works without std::move if HtmlElement::build is in the same full expression as the conversion to HtmlElement)
return 0;
}
CodePudding user response:
As far as I understand, you return a unique_ptr
with the build
method. However, in your main
function when you do this:
auto* builder2 = HtmlElement::build ...
What you actually do is retrieve the raw pointer from the unique_ptr
that is returned and then you let this temporary unique_ptr
be destroyed. Which in turn frees the instance that was handled by the unique_ptr
, which is the very same instance that you retrieved the raw pointer for.
So in the next line:
cout << (*builder2).str() << endl;
You are actually trying to dereference a pointer to invalid memory, since the instance that resided there was just deleted by the temoporary unique pointer that was destroyed in the previous line.
If you remove the raw pointer from the "auto" part like this:
auto builder2 = HtmlElement::build("ul") ...
Then you will have a smart pointer to your instance.
Then you can call the str method on the smart pointer as if it was a pointer to your instance:
cout << builder2->str() << endl;