8

Basically, I have the following situation. Note: void* is used to denote arbitrary data, it is strongly typed in a real application.

class A
{
public:
   //uses intermediate buffer but doesn't change outward behavior
   //intermediate buffer is expensive to fill
   void foo(void* input_data);

   //uses intermediate buffer and DOES explicitly change something internally
   //intermediate buffer is expensive to fill
   void bar(void* input_data);

   //if input_data hasn't changed since foo, we can totally reuse what happened in foo
   //I cannot check if the data is equal quickly, so I allow the user to pass in the
   //assertion (put the honerous on them, explicitly tell them in the comments
   //that this is dangerous to do, ect)
   void bar(void* input_data, bool reuse_intermediate);
private:
   void* intermediate_buffer_;
   void* something_;
};

So trying for const correctness, intermediate_buffer_ is never exposed so it sortof fits the definition for using a mutable variable. If i never reused this buffer, or I checked for equal input_data before using the cached values, that would be the end of the story, but because of the reuse_intermediate I feel like I'm half exposing it, so I'm not sure whether or not the following makes sense.

class A
{
public:
   //uses intermediate buffer but doesn't change something
   //intermediate buffer is expensive to fill
   void foo(void* input_data) const;

   //uses intermediate buffer and DOES explicitly change something internally
   //intermediate buffer is expensive to fill
   void bar(void* input_data);

   //if input_data hasn't changed since foo, we can totally reuse what happened in foo
   //I cannot check if the data is equal quickly, so I allow the user to pass in the
   //assertion (put the honerous on them, explicitly tell them in the comments
   //that this is dangerous to do, ect)
   void bar(void* input_data, bool reuse_intermediate);

   //an example of where this would save a bunch of computation, though
   //cases outside the class can also happen
   void foobar(void* input_data)
   {
      foo(input_data);
      bar(input_data,true);
   }
private:
   mutable void* intermediate_buffer_;
   void* something_;
};

Thoughts?

Andy
  • 1,663
  • 10
  • 17
IdeaHat
  • 7,641
  • 1
  • 22
  • 53
  • `mutable` seems reasonable. But maybe just rename it `bar(void* input_data, bool fast)` instead of suggesting there is an intermediate. Or make it `private` instead of `public` if you don't want users to hurt themselves. – Apprentice Queue Mar 26 '14 at 16:15
  • 1
    Using `T` instead of `void` would be a clearer. `void *` has special semantics. – M.M Apr 22 '14 at 00:20

7 Answers7

6

I think this is improper use of mutable. In my experience mutable is used for ancillary private member variables that by their very nature can not be declared const, but do not modify the "conceptual constness" of the public interface.

Take for example a Mutex member variable and a 'thread-safe getter':

class Getter { 
public: 

    Getter( int d, Mutex & m ) : guard_( m ), data_( d ) { };

    int get( ) const { Lock l(guard_); return data_; };

private:

    mutable Mutex guard_;
    const int data_;
};

The main point here is that the data declared mutable (in this case the guard) does change (it's locked and unlocked) but this has no impact on constness from the users perspective. Ultimately, despite the mutable Mutex, you still can't change the const data_ member variable and the compiler enforces this.

In your case, you really want the intermediate_buffer to be const but you explicitly tell the compiler it's not by declaring it mutable. The result is that you can change the data and the compiler can't do a thing about it.

See the difference?

If you really want the interface to live up to the const agreement, make it explicit via something like this:

    class A { 
    public:    

        A( void* input_data );// I assume this deep copies.

        void foo() const;

        void bar();

        private:    
            const void* intermediate_buffer_;   
            void* something_; 
    };

Now the onus is truly on the user and enforced by the compiler regardless of what the comments say and without any use of mutable. If they know that input_data has changed, they'll have to create a new one, preferably const.

Andy
  • 1,663
  • 10
  • 17
  • Alternatively, sometimes it can be helpful to have a `state` object that the caller can pass in as needed, but can't actually interact with, similar to strtok_s: http://coliru.stacked-crooked.com/a/11b375685cbd3bb2 – Mooing Duck Apr 21 '14 at 23:12
4

Instead of hiding the intermediate object inside the const object, expose it and let foo and bar return a copy. You are making it very explicit that the intermediate object is being shared, and providing a new capability to keep more than one on hand. If you want to hide the implementation details you can expose an empty class and make the intermediate object a child of this base class.

class A
{
public:
   class Intermediate
   {
      //empty
   };

   //uses intermediate buffer but doesn't change outward behavior
   //intermediate buffer is expensive to fill
   //if cache_ptr is NULL the intermediate buffer is discarded
   void foo(void* input_data, Intermediate** cache_ptr = NULL) const;

   //uses intermediate buffer and DOES explicitly change something internally
   //intermediate buffer is expensive to fill
   void bar(void* input_data, Intermediate** cache_ptr = NULL);
private:
   class IntermediateImpl : public Intermediate
   {
      //...
   };
   void* something_;
};
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1

To answer your question directly. If the function foo is const, calling it at any time should never alter the result of the next operations.

for example:

A a(some_big_data);
a.bar(some_big_data, true);

should give exactly the same result as (excluding performance differences)

A a(some_big_data);
a.foo(some_different_big_data);
a.bar(some_big_data, true);

Since foo is const, users will expect the result to be the same. If this is the case then making the buffer mutable is reasonable. Otherwise, it is probably wrong.

Hope this helps

tiridactil
  • 389
  • 1
  • 11
0

Disclaimer: I am not advocating the use of void* with my answer, I hope that this was simply for demonstration purposes and that you don't actually need to use it yourself.

If a lot of computations can be saved when reusing the same input data, then make input_data a member variable.

    class A
    {
      public:
        A(void * input_data)
        {
            set_input_data(input_data);
        }

        //Note, expensive operation
        void set_input_data(void* input_data)
        {
            this->input_data = input_data;
            fill_intermediate_buffer();
        }

        void foo() const;
        void bar() const;
      private:
        void * input_data;
        void * intermediate_buffer;
        void * something;
    };

This is just an outline obviously, without more details about what input_data, intermediate_buffer and something are or how they get used or shared a lot of details will be missing. I would definitely drop the pointers from the implementation and use std::vector. This is especially true of input_data, where you would want to store a copy of the passed-in buffer. Consider:

    A a(input);
    //modifiy input
    a.foo();

You will likely get the wrong result from using a mismatched input_data/intermediate_buffer pair.

Also note that if you don't actually need input_data for foo and bar then you can drop the void* input_data from the class, but still keep the constructor and setter that refer to it.

SirGuy
  • 10,660
  • 2
  • 36
  • 66
  • (The void* was to denote blobs of data, discussing what should actually be there is kindof out of scope) This seems dangerous for different reasons. There isn't clear ownership of "input_data", so input_data must data must remain within scope OR be copied. Basically, if the data was cheap enough to copy the input buffer, then the data would be cheap enough to verify no change. Though I agree, this fits the letter of `const`. – IdeaHat Apr 18 '14 at 15:57
  • @MadScienceDreams instead of copying input_data you could use a `shared_ptr`. Other than that, it's hard to imagine that scope is an issue here, unless you're saying that you want to optimize for calling multiple functions with the same input data, but that input_data won't be the same actual object? – SirGuy Apr 18 '14 at 17:44
0

I'd say that your use of mutable does make some amount of sense - but that it is incorrect, and possibly dangerous. If you make a function const it needs to be just that.

Imagine if you use your class in a multithreaded program, with several threads operating on the same instance of the class - for example:

thread1:

while(1) {
    (...) //do some work
    sharedA->foo(thread1Data);
    sharedA->bar(thread1Data, true);
    (...) //do some more work
    sharedA->bar(thread1Data, true);
    (...) //do even more work
}

thread2:

while(1) {
    (...) //do some different work, maybe wait for an event
    sharedA->foo(thread2Data);
    (...) //sleep for a bit
}

Since thread2 is calling a const function, it shouldn't have any influence at all on the output of calling bar from thread1 - but if the timing is right (or wrong!) it will - and due to those reasons I'd say it's wrong to use mutable to make foo const in this case, even if you're only using the class from a single thread.

sonicwave
  • 5,952
  • 2
  • 33
  • 49
  • 2
    By that argument you should NEVER use mutable, or mutex lock any function call that uses mutable. – IdeaHat Apr 18 '14 at 15:32
  • 1
    Well, I'd say that for instance a `mutable` mutex will in most cases not change the actual result from running a function (unless you're using the time spent in the function in a calculation or similar, in which case the mutex should probably not be made `mutable` in any case) - but changing the input data, as you're doing, will. – sonicwave Apr 18 '14 at 15:37
  • 1
    `const` and `mutable` have nothing to do with thread-safety. The fact that some Standard library classes are thread-safe in their `const` API does not make it a requirement to do the same with user-defined types. Especially ones that aren't intended for sharing between threads. – Ben Voigt Apr 18 '14 at 16:07
  • 1
    I've been looking up on this, and apparently it depends on if you're following C++98 or C++11. From what I can gather from http://channel9.msdn.com/posts/C-and-Beyond-2012-Herb-Sutter-You-dont-know-blank-and-blank, in C++11 `const` now actually means thread-safe (as in bitwise const or internally synchronized). In C++98 I'd say it's still up for discussion, depending on what coding standards you adhere to. I personally wouldn't let a `const` function like the one in the example pass code review. – sonicwave Apr 18 '14 at 16:21
  • @Ben Voigt - you're strictly right of course. However the current "consensus" in the community seems to be that const qualified functions should be thread safe. Of course that doesn't mean they have to. But it does mean that you violate the principle of least surprise if they're not. (Except of course if there is a good reason to expect a class to be not thread safe at all. E.g. classes that are bound to a single thread, like many GUI toolkits use.) – Paul Groke Apr 18 '14 at 17:01
  • @sonicwave: Your example is perfectly expected to have a race condition. You'd be better off discussing simultaneous calls to `const` functions. – Ben Voigt Apr 18 '14 at 17:03
0

Given that you can't check input_data for equivalence, you could perhaps take a cryptographic hash of it and use that for the comparison. This would eliminate the need for the reuse_intermediate flag.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
0

mutable is used to override the const-ness of a variable or data member. Example:

 Class A
  {
    public:
        int m;
  }

  void foo(const A& a);

In the example above, function foo would be allowed to modify a.m even though you pass a const reference. In your example I don't think you need to use mutable - it can lead to very bad designs - since you will write to that buffer.

Pandrei
  • 4,843
  • 3
  • 27
  • 44