调用 class 的方法时出现段错误

Segment fault upon calling method of class

我能够安全地调用构建器,但构建器 2 因段错误而退出。 编译器不会输出任何警告。 我想知道段错误的原因。 这段代码是一个构建器模式来组成 html。 ul 和 li 是用 emplace_back 收集的,最后调用 str() 来构建部件并将它们 return 作为字符串。

#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;
}

输出结果如下

  • 你好
  • 世界

分段错误(核心已转储)

您使用 HtmlElement::build("ul") 动态创建一个 std::unique_ptr 包含构建器的对象。这个 std::unique_ptr 对象在完整表达式的末尾被销毁,这意味着 builder2 指向的对象被销毁并且取消引用它是导致您观察到的崩溃的未定义行为。

我建议根本不要返回动态分配的构建器对象。而是将 HtmlElement::build 的定义移动到 HtmlBuilder 的定义下方。您可能还想考虑允许移动语义以避免创建不必要的对象副本:

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;
}

据我了解,您 return 使用 build 方法 unique_ptr。但是,在您执行此操作时的 main 函数中:

auto* builder2 = HtmlElement::build ...

你实际上做的是从 returned 的 unique_ptr 中检索原始指针,然后你让这个临时的 unique_ptr 被销毁。这反过来又释放了由 unique_ptr 处理的实例,它与您为其检索原始指针的实例完全相同。 所以在下一行:

cout << (*builder2).str() << endl;

您实际上是在尝试取消引用指向无效内存的指针,因为驻留在那里的实例刚刚被上一行中销毁的临时唯一指针删除。

如果像这样从“自动”部分删除原始指针:

auto builder2 = HtmlElement::build("ul") ...

那么您将拥有一个指向您的实例的智能指针。

然后你可以在智能指针上调用 str 方法,就像它是指向你的实例的指针一样:

cout << builder2->str() << endl;