2
\$\begingroup\$

I made this little function for flyweighting any type that has operator<. I followed the convention of make_unique and make_shared with make_one. Not sure if that's the best name.

// Create a single instance of an object, using
// operator< to determine equality.
// Note that we take a value instead of a constructor
// arguments because often we'll want to mutate a field,
// then create a new object, without passing in everything.
template<class T>
std::shared_ptr<const T> make_one(const T& value) {

  struct cmp {
    bool operator()(const T* a, const T* b) const {
      return *a < *b;
    }
  };

  static std::set<const T*, cmp > values;

  auto iter = values.find(&value);

  if(iter != values.end()) {
    return (*iter)->shared_from_this();
  }

  auto ptr = std::shared_ptr<T>(new T(value), [](T* p) {
    values.erase(p); delete p;
  });
  values.insert(ptr.get());
  return ptr;

}

Test code:

  struct Foo : std::enable_shared_from_this<Foo> {
    int x = 0;
    Foo(int x) : x(x) { }
    bool operator<(Foo rhs) const { return x < rhs.x; }
  };

  auto p0 = make_one(Foo{0});
  auto p1 = make_one(Foo{0});
  assert(p0 == p1);

  auto p2 = make_one(Foo{42});
  assert(p2 != p0);

Review goals:

  1. Can I avoid requiring enable_shared_from_this?
  2. Can I make it thread safe without too much trouble?
  3. Any other ideas for improvement.
\$\endgroup\$
1
\$\begingroup\$

The first thing I would note is that you should point out non obvious features of your implementation. The nice thing that is not immediately obvious is that when all external references are gone the object is automatically deleted from the make_one()::values set.

This is not really a requirement of a flyweight.
But a nice little trick here.

So if the automatic deletion is required then the answers to the question are:

Can I avoid requiring enable_shared_from_this?

No. I don't think so.

You could re-invent some reference counting model but that seems a lot of work in comparison to simply using the standard libraries.

As an alternative, You can get around it slightly by using std::set<std::weak_ptr<T>> the trouble here is that even though the resource would be cleaned up the actual space for the resource would be maintained after the object is destroyed.

If on the other hand automatic deletion is not required.

Can I avoid requiring enable_shared_from_this?

Yes. there are simpler ways that this could be written if there is no requirement to delete upon all external references going away. Now if you want it again it will still be there.

template<class T>
T const& make_one(const T& value) {

    static std::set<T> values;

    auto result = values.insert(value);
    return *(result.first);
}

Can I make it thread safe without too much trouble?

Yes simply add a lock on the make_one()

Any other ideas for improvement.

You could add the ability to move an object into your make_once() rather than copy it each time. Even better would be to allow the construction of T from its parameters.

// Move an object in your set
template<typename T>
std::shared_ptr<const T> make_one(T&& value)

// Emplace a value in your set
template<typename T, typename... Args>
std::shared_ptr<const T> make_one(Args&&... value)
\$\endgroup\$
  • \$\begingroup\$ Would need a lock in the deleter too, right? \$\endgroup\$ – Taylor Jul 19 at 0:12
  • \$\begingroup\$ Would the T&& or Args&& reduce copies even though I have to create a value to call set<...>::find? \$\endgroup\$ – Taylor Jul 19 at 0:15
  • 1
    \$\begingroup\$ @Taylor Absolutely. That is the whole point of the introduction of move semantics. Move will never be more expensive than a copy and even objects that don't support move will have a cost equal to a copy. So you it could be the same cost but usually its better. \$\endgroup\$ – Martin York Jul 19 at 16:36
  • 1
    \$\begingroup\$ @Taylor Support of the emplace build Args... is great for objects that are expensive to create (don't move) and/or expensive to copy. As it allows you to pass the arguments (which are usually small and scaler) rather than the whole object. \$\endgroup\$ – Martin York Jul 19 at 16:37

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.