Search code examples
c++encapsulation

How far to go in encapsulation?


Is there a general consensus on how deep encapsulation should extend in C++? The specific example I am looking at is whether you should restrict access to vectors in a class. Should the vector be passed with getVector(), or should it be restricted to getVectorItem(int i) and addVectorItem(int x)?

public:
    vector<int>* getVector() const;

Where getVector returns a pointer the the vector

Or

public:
    int getVectorItem(int i) const;

Where getVectorItem(i) returns whatever _vector[i] is.

Do you catch my drift?


Solution

  • Depending on the use case it can be better to have a way to access members and modify them without exposing the underlying data. In your specific example it would mean the second option is the way to go.

    public:
      int getItem(int index) const;
      void setItem(int index, int item);
    

    If the underlying data structure changes you don't have to modify how the user accesses your wrapper. If you are returning more complex data then you can just return const references to make it clear how a user is supposed to access the data.

    public:
      ComplexClass const& getItem(std::string const& key) const;
      void setItem(std::string const& key, ComplexClass const& item);
    

    As before by having the public getters/setters you prevent a user from malforming the internal data the class has and allows you to implement any kind of protection.

    As an example if a user is returned an array what's to stop them from editing the incorrect element?

    class Foo
    {
    public:
      std::vector<int> & getContainer() { return container; }
    
    private:
      std::vector<int> container;
    };
    
    Foo foo;
    std::vector<int> vector = foo.getContainer(); 
    for(int i = 0; i < 10; ++i)
      vector.push_back(i);
    
    vector[99999] = 0; // The program will crash here
    

    With a custom wrapper though we prevent the crash

    class Foo
    {
    public:
      Foo() : container(10) {}
    
      int getItem(int index) const
      { 
        if(index >= container.size() || index < 0)
          return -1; // error case
        else
          return container[index];
      }
    
      void setItem(int index, int item) 
      {
        if(index < container.size() && index > 0)
          container[index] = item;
      }
    
    private:
      std::vector<int> container;
    };
    
    Foo foo;
    for(int i = 0; i < 10; ++i)
      foo.setItem(i, i);
    
    foo.setItem(99999, 0); // Nothing happens
    

    You can even add iterator operator overloads to make the accessing look exactly like accessing a vector. However if you are creating a wrapper around a container make sure to implement an iterable interface.