Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mark a member function as const when it is conceptually not

As far as I read here and there, const should be used when possible. However, I have a case that always bothers me.

Should I mark a member function as const when it does not alter any member variable values but it is not conceptually a const function?

For example:

class Engine{
public:
    int status;
};

class Car{
public:
   void start() const{
        engine_->status = 1;
    }
private:
    std::unique_ptr<Engine> engine_;
};

The compiler will accept the constness of start() since engine_ as a pointer did not change. However, It seems so unrealistic, at least IMO, that a function called start in a class called Car is a const one!

The example was just a quick one. Usually, some internal states of the Car class should be updated accordingly making const keyword non-feasible. However, the mini example was just to illustrate my idea.

like image 779
Humam Helfawi Avatar asked Feb 07 '18 15:02

Humam Helfawi


4 Answers

One simple metric for whether a function should be const is this:

Type a{...};
Type b{...};

bool comp1 = a == b;

b.some_func(...);

bool comp2 = a == b;

If comp1 and comp2 can ever be different, then some_func is not const.

Obviously, not every type has an operator== overload, but most have at least the conceptual idea of what you would test to see if they're equal. Different Car instances with different engine states would be unequal. Therefore, a function that changes the engine state is not const.

like image 178
Nicol Bolas Avatar answered Sep 19 '22 17:09

Nicol Bolas


In your case compiler allows you to make start() const due to imperfect propagation of constness through pointers. If you replace your pointer with object of type Engine your question will disappear. So answer is no, it should not be const in this case as using Engine as a smart pointer or instance is internal details and should not affect public interface of class Car.

As far as I read here and there, const should be used when possible.

This statement is way too generic, and as with any generic suggestion should not be used formally in every case.

like image 40
Slava Avatar answered Sep 21 '22 17:09

Slava


In your example, you might want std::experimental::propagate_const:

class Car{
public:
   void start() { engine_->status = 1; }
private:
    std::experimental::propagate_const<std::unique_ptr<Engine>> engine_;
};

Then your start can no longer be const.

like image 23
Jarod42 Avatar answered Sep 18 '22 17:09

Jarod42


The meaning of const can vary.

  • Something is const if it preserves ==.

  • Something is const if your type follows reference semantics and it doesn't change what is referred to.

  • Something is const if it can be used on any rvalue or lvalue in sensible ways.

  • Something is const if it is safe to use from multiple threads.

  • Something is const if it compiles as const.

  • Something is const if whatever state the object claims is internal is not mutated by it.

All of these are reasonable rules to decide if a method or argument is or is not const.

A thing to be extremely careful of is to know the difference between T const* and T*const, and don't accidentally hse top-level const as an internal const. It isn;t const iterator it is const_iterator. It isn't const gsl::span<int>, it is gsl::span<const int>. It isn't const unique_ptr<T>, it is unique_ptr<T const>.

On the other hand, vector is a value semantics typr; it pretends its buffer is a part of it (even though this is a lie). It isn't vector<const T>, it is const vector<T>.

like image 32
Yakk - Adam Nevraumont Avatar answered Sep 19 '22 17:09

Yakk - Adam Nevraumont