Skip to content

Instantly share code, notes, and snippets.

@tk3369
Last active February 22, 2023 01:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tk3369/5327541b24f0d84a70739ba3981c7b66 to your computer and use it in GitHub Desktop.
Save tk3369/5327541b24f0d84a70739ba3981c7b66 to your computer and use it in GitHub Desktop.
C++ builders

Working with a hierarchy of builders that builds objects from a class hierarchy

I have a class hierarchy in which every class has some defined properties. I want to use builder pattern for all of these classes. Since the classes are set up in a hierachy, ideally I would do the same with the builders. The build function can validate its parts before constructing the concrete object. This guarantees that all necessary invariants are met.

Primary design

Basic design:

  1. Every class has private members for storage and provide getters for those members.
  2. I want to avoid people from constructing these classes directly, so the constructor has protected visibility.
  3. The builders must have access to the constructor also hence they can be declared as a friend.
  4. Use a macro to define storge & setter, and use that in the builder class.

Note that the builders do not form a hierarchy because setters cannot be inherited properly as the setter method of a sub-builder that is inherited froma parent builder must return the sub-builder class rather than parent builder class.

Pros

  1. Easy to use since I can chain setters and the build method

Cons

  1. Hard to maintain because every sub builder must repeat the definition of parent fields
  2. A macro is used to ease the pain of duplicate code but it is less idiomatic in C++
#include <stdexcept>

// forward decl
class BaseBuidler; 
class SubBuilder; 

// immutable objects
class Base {
public:
    int get_x() { return x_; }
    int get_y() { return y_; }
protected:
    friend class BaseBuilder;
    Base(int x, int y) : x_(x), y_(y) {}
private:
    int x_;
    int y_;
};

class Sub : public Base {
public:
    int get_z() { return z_; }
protected:
    friend class SubBuilder;
    Sub(int x, int y, int z) : Base(x, y), z_(z) {}
private:
    int z_;
};

// builders
#define PROP(B, T, v) \
private: \
    T v##_; \
public: \
    B set_##v(T val) { v##_ = val; return *this; } \
private: \

class BaseBuilder {
    PROP(BaseBuilder, int, x)
    PROP(BaseBuilder, int, y)
public:
    Base build() {
        return Base(x_, y_);
    } 
};

class SubBuilder {
    PROP(SubBuilder, int, x); // dup from BaseBuilder
    PROP(SubBuilder, int, y); // dup from BaseBuilder
    PROP(SubBuilder, int, z);
public:
    Sub build() {
        return Sub(x_, y_, z_);
    }
};

int main() {
    Base b = BaseBuilder().set_x(10).set_y(10).build();
    Sub s = SubBuilder().set_x(1).set_y(2).set_z(1).build();
    return s.get_z();
}

An alternative design

The setters for the builder class here returns void rather than the builder class itself.

Pros

  1. No need to use macro as there is no duplicate code anymore

Cons

  1. The code is more verbose since the user cannot chain the setters and build method.
// use builders
#include <stdexcept>

// forward decl
class BaseBuidler; 
class SubBuilder; 

// immutable objects
class Base {
public:
    int get_x() { return x_; }
    int get_y() { return y_; }
protected:
    friend class BaseBuilder;
    Base(int x, int y) : x_(x), y_(y) {}
private:
    int x_;
    int y_;
};

class Sub : public Base {
public:
    int get_z() { return z_; }
protected:
    friend class SubBuilder;
    Sub(int x, int y, int z) : Base(x, y), z_(z) {}
private:
    int z_;
};

// Builders

class BaseBuilder {
public:
    void set_x(int x) { x_ = x; }
    void set_y(int y) { y_ = y; }
    Base build() {
        return Base{x_, y_};
    }
protected: 
    int x_;
    int y_;
};

class SubBuilder : public BaseBuilder {
public:
    void set_z(int z) { z_ = z; }
    Sub build() {
        return Sub(x_, y_, z_);
    }
protected:
    int z_;
};

int main() {
    
    BaseBuilder builder;
    builder.set_x(1);
    builder.set_y(2);
    Base b = builder.build();

    SubBuilder sb;
    sb.set_x(1);
    sb.set_y(2);
    sb.set_z(10);
    Sub s = sb.build();

    return s.get_z();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment