Should you use const for the members that store the interface references in the mockable interfaces idiom?

This article is part of a series of articles on programming idioms.

Const attempt

With good intentions developer try to use const as much as possible.

Consider this foo class from the mockable interfaces article, but with as much const as possible (example; bad):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class foo : public foo_interface {
  const bar_interface & bar_;
  const buzz_interface & buzz_;
  const std::string fred_;

public:
  foo(const bar_interface & bar,
      const buzz_interface & buzz,
      const std::string & fred):
    bar_{ bar }, buzz_{ buzz }, fred_{ fred }
  {}

  foo(const foo &) = delete;
  foo & operator=(const foo &) = delete;

  // implement in terms of bar_, buzz_ and fred_
  void some_fn() const override;
  int some_fn2() const override;
};

Once we made the member references const, in order to be able to call methods on the interface, they have to be const (example; bad):

1
2
3
4
5
struct bar_interface {
  virtual void bar_fn() const = 0;

  virtual ~bar_interface() = default;
};

What’s wrong with it

At least three things.

First it allows now accidentally creating foo with temporary bar and buzz that go out of scope as soon as the constructor completes.

1
2
3
4
5
int main() {
  foo x(make_bar(), make_buzz(), "flintstone");
  // ups, x has dangling references now
  // to temporaries that have been destructed
}

Second it’s semantically wrong. For data, like in our regular data and functions idiom, const means const. In the mockable interfaces idiom, the classes are doers. E.g. one such class might access a database and read/write to from the database, so a sequence read(), write(), read() might get a different result for the second read despite all methods being const. Here we’re just abusing the syntax, technically it is correct that we don’t change member variables, but with no benefit in the ability to reason about the code, that you would expect const to bring.

Third. In test mock class, behind a macro like MOCK_METHOD the test framework stores a state machine as a member variable so that EXPECT_CALL methods configure that state machine and when a method is called, the mock implementation goes through the state machine to check the expectations. If the mocked method is const, the state machine will be declared mutable to satisfy the complier that would complain about changing members from a const method. Often an interface is implemented twice: one for the actual implementation to be used in the delivered program and the other by a mock to be used in unit tests and for 50% of the cases the const methods are not really const.

Forth: multi-threading. In general const is useful to address some threading concerns, but on it’s own it’s not sufficient. Just marking interface functions const will not make your object thread safe.

Conclusion

Interface methods don’t play well with const. There is a contradiction between using const on a method, which limits what the method can do, and having the method declared in an interface as pure virtual, which means it can choose to do what it pleases.