具有结构的构建器模式

Builder pattern with struct

我正在尝试用 C++ 实现构建器模式。那是我现在得到的:

struct Person {
  std::string name;
  uint32_t age;
  std::vector<std::string> pet_names;
};


class PersonBuilder {
public:
  PersonBuilder& SetName(std::string name) {
    person_.name = std::move(name);
    return *this;
  }

  PersonBuilder& SetAge(uint32_t age) {
    person_.age = age;
    return *this;
  }

  PersonBuilder& SetPetNames(std::vector<std::string> pet_names) {
    person_.pet_names = std::move(pet_names);
    return *this;
  }

  Person Build() {
    return Person(std::move(person_));
  }

private:
  Person person_;
};

int main() {
  auto person = PersonBuilder()
      .SetName("John")
      .SetAge(23)
      .SetPetNames({"rax", "rbx", "rcx"})
      .Build();

  return EXIT_SUCCESS;
}

我有点担心 uint32Build 方法中的 std::move(person_)。我做得对还是有更好的方法,例如使用结构构造函数?

I'm a little worried about unitilized uint32

你在使用它之前先初始化它,所以这个例子没有问题。然而,构建器的用户很容易忘记调用其中一个设置器,从而留下不确定的值,从而导致未定义的行为。你给它一个默认的成员初始化器来避免这种情况。

and std::move(person_) inside a Build method.

没有必要在这里重复 class 的名称。这也行得通:

return std::move(person_);

调用者了解 Build 每次初始化只能调用一次也很重要。如果构建器仅像示例中那样用作临时对象,则可以将右值引用限定符添加到 Build 函数以防止某些意外误用。


or there is a better way,

我看不到生成器的好处。为什么不直接使用聚合初始化?:

Person person {
    .name = "John",
    .age = 23,
    .pet_names = {"rax", "rbx", "rcx"},
};

你做对了。

也可以把set函数变成模板函数,这样可以避免在为Person添加新的成员变量时修改PersonBuilder

struct Person {
  std::string name;
  uint32_t age;
  std::vector<std::string> pet_names;
};

class PersonBuilder {
public:
  template<auto mem_ptr>
  PersonBuilder& Set(std::remove_reference_t<std::invoke_result_t<decltype(mem_ptr), Person&>> value = {}) {
    std::invoke(mem_ptr, person_) = std::move(value);
    return *this;
  }

  Person Build() {
    auto result = std::move(person_); 
    person_ = Person(); 
    return result;
  }

private:
  Person person_;
};

int main() {
  auto person = PersonBuilder()
    .Set<&Person::name>("John")
    .Set<&Person::age>(23)
    .Set<&Person::pet_names>({"rax", "rbx", "rcx"})
    .Build();
}

Demo.