jafasi
jafasi

Reputation: 25

What is the preferred way to store one or no object in c++?

In the spirit of "choose your containers wisely", I am interested in what is the best way to store either exactly one or no object, for example as a member in a class. This could be the case, e.g., if the object being held is expensive to calculate and should be cached in some way (or any other type of "late" creation).

The obvious candidates are std::vector and std::unique_ptr, for example:

class object_t;
class foo_t {
  std::unique_ptr<object_t> m_cache;
  public:
    object_t getObject() {
      if( not m_cache ) {
        m_cache.reset(new object_t()); // object creation is expensive
      }
      return object_t(*m_cache);
    }
};

and similarly with vector (or almost any other container):

class object_t;
class foo_t {
  std::vector<object_t> m_cache;
  public:
    object_t getObject() {
      if( m_cache.empty() ) {
        m_cache.push_back(object_t()); // object creation is expensive
      }
      return m_cache.front();
    }
};

Of course, there is still the possibility to have some boolean variable, which holds the state of the object:

class object_t;
class foo_t {
  bool cache_healthy;
  object_t m_cache;
  public:
    foo_t() : cache_healthy(false), m_cache() {}
    object_t getObject() {
      if( not cache_healthy ) {
        m_cache = object_t();
        cache_healthy = true;
      }
      return m_cache;
    }
    /* do other things that might set cache_healthy to false. */
};

From the three examples, I like the last one the less, because it either creates the object twice, or, if I change object_t to have a "cheap" / incomplete constructor, might return a invalid object.

The solution with the vector I dislike more semantically, because a vector (or any other container type) might give the impression that there might be more than just one object.

Now thinking of it again, I think I like the pointer solution most, however, still am not entirely happy with it and would like to hear if you know of any solution that is the most elegant in this case.

Upvotes: 0

Views: 86

Answers (1)

filmor
filmor

Reputation: 32298

The "obvious" solution is using boost::optional or (in C++17) std::optional.

An implementation of something like this could look like the following:

template <typename T>
class optional
{
public:
    optional() : m_isset(false) {}

    template <typename ...Args>
    optional(Args... args) {
        m_isset = true;
        new (&m_data[0]) optional { args... };
    }

    // overload operator-> and operator* by reinterpret_casting m_data, throwing exceptions if isset == false

private:
    bool m_isset;
    char m_data[sizeof(T)];
}

The disadvantages of your solutions are unneeded heap allocation in 1 and 2 and reliance on a copy in 3.

Upvotes: 1

Related Questions