mercredi 20 octobre 2021

What is the correct pattern to define a class that accepts a const and non-const object without violating const correctness?

There are a number of obvious problems with this class and it is meant to illustrate the problem rather than demonstrate working code.

#include <list>

template<typename T>
class OnlyLists {
private:
   const std::list<T>* p_list;
   
public:
   OnlyLists(std::list<T>* list) : p_list(list) { }
   OnlyLists(const std::list<T>* list) : p_list(list) { }
   
   void modifiesList() {
      const_cast<std::list<T>*>(p_list)->clear();
   }
   
   size_t viewsList() const {
      return p_list->size();
   }
};

int main() {
   const std::list<int> const_list;
   
   // Constness is preserved
   const OnlyLists<int> a(&const_list);
   
   // Constness is discarded
   OnlyLists<int> b(&const_list);
   
   // Constness is discarded (another inherited problem)
   b = a;
   
   return 0;
}

I want to create a class that can view and modify a particular object (a list in this example). Its methods are exposed such that only the const ones can modify the list. The problem is that this constness on the methods only relates to whether the OnlyLists object is const. And if I accept a const list<T>* without also declaring the OnlyLists object as const then I can modify the list when I shouldn't.

It's possible to work around this by making the constructors private and making object creation a static method to ensure constness but this approach throws away public constructors entirely and is instead an anti-pattern. Additionally, keeping only one pointer p_list would already requires constness to be cast away when calling any method that would modify the list.

What is the correct pattern in C++ where I want one class to be able to view and modify another object like this?

The only solution that I can see is that any class which accepts both const and non-const types can only provide a const view. But the only issue here is with the object construction; the actual interface already protects the object from modification.

I thought about moving the entire list type into the template but this seems redundant if the class already accepts only lists. If I move the entire container into the template then constraints would need to be added to ensure that it is a list, etc. This seems overly complex when the constructor already limits itself to accepting only lists.

Aucun commentaire:

Enregistrer un commentaire