15

I often have a problem with const correctness when wrapping algorithms in classes in c++. I feel that I want a mutable function, although this is not allowed. Can anyone advise me how to implement classes such as the one that follows?

The following is the code that I want to write.

  • The function run() should not be a const function because it changes the data.
  • The function get_result() should be a constant function (as far as the user is concerned) because it returns the data.

However, if the user requests the result without calling run(), I want the get_result() function to run the algorithm. This breaks the const correctness because I have a const function calling a non-const function.

class operate_on_data
{
  std::vector<double> m_data;  // the data to modify
  bool m_completed;  // check to see if the function run() has been called
public:
  operate_on_data(std::vector<double> data)
    : m_data(data), m_completed(false) {}  //initialise
  void run() //I don't want this function to be const  
  {
    //The algorithm goes here - it alters m_data.
    m_completed = true;  //the algorithm has been run
  }
  std::vector<double> get_result() const //I want this function to be const
  {
    /*The following breaks const correctness because 
      I am calling a non-const function from a const function
      - but this feels like the right thing to do ... */ 
    if (!m_completed) run();  //run the algorithm if it has not run already
    return m_data; //return
  }
};

The only way I have been I have managed to compile the above class is to either

  • make run() const, and make m_data and m_completed mutable. This works but is conceptually wrong because run() demonstrably changes data.
  • make get_result() not a constant function. This seems wrong too, for the user would expect this function to be a simple return, and therefore constant.
  • Put the run() function into the get_result() const function and make the data variables mutable.

My understanding was that the mutable keyword was designed for the third of these options, where the implementation requires data to be changed but the user reasonably expects a simple return and therefore const function.

However, I don't want to do this final option because I want the user to be able to choose exactly when they change the data. There is a chance that they will forget to call run(), however, and so I want to force the algorithm if they request the result without calling run(). I feel like a want to make run() mutable - but I'm not allowed to.

What is the correct way to write such a class?

4

8 回答 8

11

make run() const, and make m_data and m_completed mutable. This works but is conceptually wrong because run() demonstrably changes data.

Not true, actually. The variables within your class are, in fact, altered but you could never, ever demonstrate this. Calling run() does not change anything that the user is able to retrieve from your class's interface. If you can't retrieve any information about such a change then you can't possibly demonstrate that change. This is more than a semantic issue, it speaks directly to the whole point of the 'mutable' keyword.

The 'mutable' keyword is greatly misunderstood.

That said, though with the minimally given information I have I might do it the above way, I'm not recommending it. There's almost certainly a better method that would be apparent given a larger view of your problem.

The other method I might use is what you're apparently set on avoiding: force the user to call run() before using get_data(). To tell the truth though, this is a really suboptimal method too. Perhaps more so.

Edit:

If you do decide to use the mutable method then I'd suggest some changes. Having a function called 'run()' that is const and returns nothing of interest would be quite confusing. This function should certainly be non-const. Thus, what I would do, given a decision to do it this way already, is have run() call a const and private function that has the behavior of the current 'run()' function, which is also referred to by get_data() under the specified conditions.

于 2010-11-22T18:20:24.653 回答
6

Some abstract remarks which may help you clarify things:

  • const methods are those which don't modify the conceptual "state" of an objects,
  • non-const method are those which do.
  • Additionally, mutable fields are those which are per-object, but not considered a part of the object's conceptual state (like some cached values which are evaluated lazily and remembered).

The problem might be that operate_on_data may not really be a well-defined class. What is an object of class "operate_on_data"? What is the "state" of this object? What is not? This sounds awkward (at least to me) - and awkward-sounding description of some design may indicate counter-intuitive design.

My thought is that you're keeping the different concepts of an "operation" and an "operation result" in one strange class, which leads to confusion.

于 2010-11-22T18:07:23.517 回答
3

I think your problem is a semantic, not a syntactic one.

Requesting the result without calling run() first is an error, in my eyes, and should result in an exception.

If it is not an error and should indeed be possible, I see no sense in having run() in the first place, so just drop it and do all the work in the (non-const) get_result().

于 2010-11-22T18:06:11.653 回答
2

Just make it an error (part of the interface) to get the result prior to running the algorithm. Then you separate the work from the results, allowing both to properly indicate their constness. The get method can throw if you attempt to call it prior to running the algorithm to indicate to the client they're doing something wrong.

于 2010-11-22T18:03:27.517 回答
1

If get_result() may actually change data, it is not const. If you want it to be const, don't call run() but rather throw an exception.

You should use mutable for cached data, i.e. things that do not change the state of your instance and are only stored for performance reasons.

于 2010-11-22T18:06:57.227 回答
0

If I ever come into that position, I'd probably throw an exception.

However, you may get away with

if (!m_completed) (const_cast <operate_on_data *> (this))->run();

However, if get_result is then called on an instance of operate_on_data that has actually been defined to be const, you enter lala-land.

于 2010-11-22T18:07:12.317 回答
0

If the only thing that run() changes in the object is m_completed then it's fine to declare m_completed mutable and run() const. If run() changes other things, then calling get_result() will also change those other things, meaning get_result() should definitely not be const.

However, to round out the discussion, you will notice that the STL containers each have two begin() functions and two end() functions. One begin() and one end() function will return mutable iterators, while the other begin() and end() functions will return const_iterators.

It is in fact possible to overload run() with a const and non-const version. Then get_result() will call the const version of run() because it will be considered the only legal option:

class operate_on_data
{
    std::vector<double> m_data;
    bool m_completed;
public:
    operate_on_data(std::vector<double> data)
        : m_data(data), m_completed(false) { }
    void run()
    {
       //The algorithm goes here - it alters m_data.
       m_completed = true;
    }

    void run() const
    {
        // something that does not modify m_data or m_completed
    }
    std::vector<double> get_result() const
    {
        if (!m_completed)
            run();
        return m_data;
    }
};

However, that only makes sense if the const version of run() doesn't change any state. Otherwise the non-const-ness of run() will leak into get_result() and make the const-ness of get_result() a blatant lie.


I assume that the example code is somewhat contrived. If not, you're essentially taking this:

std::vector<double> results = do_calculation(data);

And wrapping it in an object with a very thin interface (namely, a get_results() method that returns the std::vector<double>). I don't see much of an improvement in the object-ized version. If you want to cache the results of your calculation, it usually makes a lot of sense to do that by simply keeping the std::vector<double> around int the code that would have created and used this object.

于 2010-11-22T18:28:54.347 回答
0

The concept of const is up to the implementation of the class; what's important is the logical semantics, not field level / function level constness. Therefore, if one needs to call a non-const method from const, simply const cast. I prefer:

void X::foo(  ) const {
      X & self = const_cast<X &>(*this);
      self.bar( ); //"bar" non-const function
}
于 2015-02-10T15:12:33.220 回答