1

I have a C++ class A that can be constructed to perform a certain computation using a function A::compute . This function requires to write to a preallocated memory area (working area) that was allocated at construction of A to perform this computation efficiently. I would like to A::compute to be const in relation to the class, because the computation does not alter the logical state of the object.

Is this a case when the keyword mutable should be used?

Example code:

class A {
public:
   A(size_t size) : m_workingArea(size) {}

   int compute(const std::vector<int>& data) const {
      // ... checks ...
      std::copy(data.begin(), data.end(), m_workingArea.begin());
      // ... compute ...
      // return a result, say first element of the working area
      return m_workingArea[0];
   }
private:
   mutable std::vector<int> m_workingArea;
};
hellman
  • 87
  • 5
  • 1
    No, it seems to be a misuse. "_I would like to `A::compute` to be `const` in relation to the class, because the computation does not alter the logical state of the object._" - except, it does. I would just drop the `const` qualifier on `compute` and `mutable` on `m_workingArea`. – Ted Lyngmo Oct 19 '21 at 16:06
  • 6
    Are you sure you need a recyclable preallocated temporary in the first place? That looks like a premature microoptimization. I would start by writing readable code (in this case with the temporary being actually temporary in the method), measure that, then make the hotspots more complicated. –  Oct 19 '21 at 16:11
  • 1
    @dratenik: Your question is a valid one. It should rarely be the first step to optimize like this. However, sometimes you *do* need these kinds of optimizations, and then the question of mutability comes up. – Emil Oct 20 '21 at 19:18

2 Answers2

1

It is perfectly reasonable to use mutable in this case.

While it is not physically const, the compute(...) method is logically const. That is, to an outside user, compute(...) leaves the object unchanged, despite any internal changes.

Here in the isocpp.org FAQ, the Standard C++ committee recommends that we should prefer logical const over physical const.

Given this, it makes sense to mark m_workingArea mutable, so that compute(...) can be const and logical const-correctness can be maintained. The effect of this should be a clear user interface and clearly stated programmer intent.

Emil
  • 182
  • 1
  • 5
0

To me it looks like a misuse since you are changing the object, but if you really want a fixed size area that you can modify even when this is const, you could keep a pointer to that area and then you don't need to make it mutable:

#include <memory>
#include <vector>

class A {
public:
   A(size_t size) : m_workingArea(std::make_unique<int[]>(size)) {}

   int compute(const std::vector<int>& data) const {
      // ... checks ...
      std::copy(data.begin(), data.end(), m_workingArea.get());
      // ... compute ...
      // return a result, say first element of the working area
      return m_workingArea[0];
   }

private:
    std::unique_ptr<int[]> m_workingArea;
};
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
  • 2
    Using a pointer instead of `mutable` seems like it would obscure the intended use of `m_workingArea`, and the `get()` method of a smart pointer should be avoided unless necessary (e.g. interfacing with legacy code or a C interface that requires actual pointers). If `m_workingArea` is just a vector marked `mutable`, then it's clear to any future people working on the code that the class is logically const but not bitwise const. – jjramsey Oct 20 '21 at 17:28
  • @jjramsey It's an _owning_ pointer to an external area which makes it perfect for separating the part of the object that is actually used to keep state from the workarea it has allocated. Nothing is obscured - and you also do not need to mark anything `mutable` because the object is in fact both logically _and_ bitwise `const`. I'm not sure where you've gotten that view on `get()` but I find it odd. Would `&m_workingArea[0]` be a better option when you have a `unique_ptr` and want to interact with standard algorithms? – Ted Lyngmo Oct 20 '21 at 18:14
  • But it's only bitwise const in a very narrow sense, and one that isn't very useful. Practically speaking, if an object owns the pointer to some content, then that content might as well be part of the object. And no, I wouldn't recommend using `&m_workingArea[0]` over `m_workingArea.get()`. Rather, I'd say that `get()` is a code smell because it exposes a raw pointer (which should be done as little as possible), and a sign that the wrong type is being used for `m_workingArea`. – jjramsey Oct 20 '21 at 18:30
  • @jjramsey I party agree. The area the owning pointer points at could be seen as an extension of the object itself. In this case, it's the non-`const` extension, neatly separated from the rest of the body. The pointer, a handle to a system managed resource, won't change - it's `const`, but it can be used to manipulate that system managed resource. When it comes to `unique_ptr` and `get()` you pretty much say that there is always a code smell to use a `unique_ptr` with standard algorithms - which sort of questions its existence. One should always go for `array` or `vector`? – Ted Lyngmo Oct 20 '21 at 20:35
  • 1
    See this SO answer about the usage of `unique_ptr` as a tool of last resort: https://stackoverflow.com/a/16711846/1643973 Here, you are using `unique_ptr` as a workaround to allow a `const` member function to modify data owned by a object. But this workaround has the serious downside that *it doesn't stand out as a workaround*. It's not obvious at first glance why a `unique_ptr` is being used. The keyword `mutable`, on the other hand, communicates that such a workaround is taking place. – jjramsey Oct 21 '21 at 12:44
  • @jjramsey I don't see it as a workaround. I see the pointer as an opaque handle to a resource managed by the system. The work area in it self could just as well be managed by a user defined manager and the pointer is this object's key into that store. Having the work area is just a way to circumvent having to do a potentially expensive allocation locally in the function - which may or may not be a premature optimization. – Ted Lyngmo Oct 21 '21 at 13:04