Search code examples
c++c++11entity-component-system

How do I create a class with reference member and vector of pointers?


I am fairly new to C++ and am working on a personal project. I want to create a vector<Entity*> entitities in C++ where each Entity object is unique. I have a class inside header Entity.h that I want to create. Now Entity takes two member variables:

  1. Rectangle rect - an Object of type Rectangle that has four float variables as member variables (x1, y1, x2, y2) which are the coordinates of two opposite corners of a rectangle
  2. vector<Component*> - a number of different components of Base class type Component with some Derived classes. The code for Class Component looks like so:
/*************************** BASE CLASS **************************/
class Component
{
public:
    virtual ~Component() = default;
    virtual Component* Clone() = 0;
};

/*************************** DERIVED CLASS 1 **************************/
class BasicComponent: public Component
{
private:
    int B= 0;
public:
    BasicComponent* Clone() override {
        return new BasicComponent(B);
    }

    BasicComponent(const int& mB) :B(mB){}
    BasicComponent() :B(0) {}

};

/*************************** DERIVED CLASS 2 **************************/
class AdvancedComponent: public Component
{
private:
    float A = 0.f;
    int iA = 0;
public:
    AdvancedComponent* Clone() override {
        return new AdvancedComponent(A, iA);
    }

    AdvancedComponent(const float& mA, const int& miA) :A(mA),iA(miA) {}
    AdvancedComponent() :A(0.f),iA(0) {}

};

Since I want each Entity in the vector of entities to be unique, that is, have it's own rectangle and components, how should I create the class ?

My question here is, what should the class Entity look like ? Should I create separate CopyConstructor, Assignment Constructor and Destructor for this class ? Also if I want to implement copying one Entity into another (deep copying), is it necessary to have all 3 (Copy, Assignment and Destructor) ?


Solution

  • My question here is, what should the class Entity look like ? Should I create separate CopyConstructor, Assignment Constructor and Destructor for this class ? Also if I want to implement copying one Entity into another (deep copying), is it necessary to have all 3 (Copy, Assignment and Destructor) ?

    The answer to this doesn't really depend on what the Entity looks like, but on what semantics it is intended to have.

    You say every Entity is "unique", but every object in C++ (even every int, even when they happen to have the same value) is technically unique. So what do you really mean?

    1. Should an Entity be copyable? Copy-constructing an Entity would mean two Entity objects have the same contents (literally if the component pointers are shallow-copied, and logically if they're deep-copied).

      If not, you probably don't want to write a (or may explicitly delete the) copy constructor and/or copy-assignment operator.

    2. Should an Entity be moveable? Probably yes, since it doesn't violate uniqueness and makes them easier to use efficiently.

      If so, you should make sure it has a move constructor and move-assignment operator (either by writing it or arranging for the compiler to generate useful defaults).

    Also if I want to implement copying one Entity into another (deep copying)

    That seems to violate your uniqueness constraint, but yes, you'd want a copy constructor and copy-assignment operator for this.

    However, best practice is to avoid interleaving resource management with your program logic. So, instead of writing all these, consider having a smart pointer automate it for you. See for comparison the Rule of Zero mentioned in this answer.

    In fact, we can illustrate all the reasonable semantics with very little code:

    template <typename ComponentPtr>
    struct ZeroEntity
    {
      Rectangle bound;
      std::vector<ComponentPtr> components;
    };
    
    using ShallowCopyZeroEntity = ZeroEntity<std::shared_ptr<Component>>;
    using NoCopyOnlyMoveEntity = ZeroEntity<std::unique_ptr<Component>>;
    using DeepCopyZeroEntity = ZeroEntity<my::clone_ptr<Component>>;
    

    except for the fact that we still need to write a deep-copying clone_ptr, something like

    namespace my {
    template <typename T>
    class clone_ptr
    {
      std::unique_ptr<T> p_;
    
      std::unique_ptr<T> clone() const { return std::unique_ptr<T>{p_ ? p_->Clone() : nullptr}; }
    
    public:
    
      using pointer = typename std::unique_ptr<T>::pointer;
    
      explicit clone_ptr(pointer p) : p_(p) {}
    
      // copy behaviour is where the cloning happens
      clone_ptr(clone_ptr const& other) : p_(other.clone()) {}
      clone_ptr& operator=(clone_ptr other)
      {
        other.swap(*this);
      }
    
      // move behaviour (and destructor) generated by unique_ptr
      clone_ptr(clone_ptr&& other) = default;
      clone_ptr& operator=(clone_ptr&&) = default;
    
      // now write all the same swap, release, reset, operator* etc. as std::unique_ptr
    };
    }
    

    ... and if that looks like a lot, imagine how messy it would have been interleaved with your Entity code instead of collected into one place like this.