Explain C ++ Mutability via Indirection

In C ++ Programming Language, Fourth Edition, Section 16.2.9.4 "Mutability through Indirection" has a sketch of an example of using indirection instead of the mutable

lazy evaluation keyword .

struct cache {
    bool valid;
    string rep;
};

class Date {
public:
    // ...
    string string_rep() const;
private:
    cache * c;    // initialize in constructor
    void compute_cache_value() const;
    // ...
};

string Date::string_rep() const {
    if (!c->valid) {
        compute_cache_value();
        c->valid = true;
    }
    return c->rep;
}

      

Complete working example .

There are few explanations:

A member declaration is mutable

most appropriate when only a small part of the representation of a small object is allowed to change. More complex cases are often better handled by putting the changing data in a separate object and accessing it directly.

I am looking for a more complete explanation. In particular,

  • What is smallness limitation? Is it a small amount of memory or a small amount of logic?
  • Doesn't initialize c

    in constructor victory (to a non-trivial degree) laziness? That is, it really works, you never need to.
  • Why a c

    bare pointer instead of something like this unique_ptr

    ? The previous chapters have gone to great lengths to demonstrate the safety of exceptions and RAII.
  • Why not just have a member mutable cache c

    if you're going to allocate and initialize c

    in the constructor anyway?

In other words, is this a real pattern or a contrived example to demonstrate indirection and constant?

+3


source to share


2 answers


The "small constraint" is not a real constraint, just a hint on how to write the code (and it has little to do with memory usage). If you have a class with 30 members and 20 of them are mutable, it might make sense to split it into two classes (one for the mutable part and one for the rest).

Why it's not a smart pointer: I don't know, but probably too tired of the author of the book: p



Why is this a pointer to everyone: You're right, it doesn't have to. Creating a mutable cache object without any pointer will work too, and if no pointer is required otherwise (like getting an existing object from the outside), the pointer only adds another opportunity to generate errors.

+4


source


Warning: None of the following texts are of practical value to programmers on the battlefield, not to philosopher programmers or ordinary philosophers. Also, I'm a big fan of Bjarne Stroustrup and my opinion may be biased. Unfortunately the StackOverflow format is not suitable for book discussions.

In addition, we discuss awkward constant volatility issues where we have to lie to the compiler and the user of the class. And there is no consensus. I'm ready to comment and cut;)

In short:

  • you probably didn't understand what exactly lazy initialization means (perhaps because the term was not chosen correctly in the book) As with RAII, we know that Bjarne is notoriously poor at verifying terminology;)
  • There are several decisions to make when writing a programming book. So some of the questions boil down to "How to write a book?" not "How do I write production code?"


Long:

  • What is the limitation on smallness? Is it a small amount of memory or a small amount of logic?

    I'll quote Bjarne again:

    Declaring a mutable member is most appropriate when only a small portion of the view for a small object is allowed to change

    I think he meant "small number of data members" here. Refactoring by grouping data into a separate class is good advice in general. What is the ratio between "fit" and "small"? You decide it yourself (given the real problem, the profiling tool and memory / speed limits / battery_life / money / client_happiness, etc.).

  • Doesn't c in its constructor fail to (to a non-trivial degree) laziness? That is, it really works, you never need to.

    Well, by lazy initialization we mean not calculating the correct string value (i.e. compute_cache_value()

    ) every time the user requests a string, but only when it really needs to be. Not initializing with an empty string, is it? ( std::string

    initializes an empty string on build)

    In chapters 16.2.9.3 and 16.2.9.4 there are no constructors in the Bjarne code! And you also don't evaluate the string in the constructor in your code, but initialize it with an empty string literal. All calculations are delayed until the last moment. So, lazy initialization works great for me here.

    As an additional "strike" of premature optimization, if you want true lazy initialization, you could leave the pointer cache*

    uninitialized in the constructor and allocate on the first call Date::string_rep()

    . This will be a heap safe heap if your cache is large and if the user never needs it. And this way you wrap the computation in the constructor cache

    , which makes lazy evaluation really lazy initialization

  • Why with a bare pointer instead of something like unique_ptr? the previous chapters have made a little effort to demonstrate the security exception and RAII.

    In "C ++ Programming Languages ​​4th Edition," smart pointers are introduced in Chapter 17, and we're talking about Chapter 16. Also, it doesn't really make sense to describe mutability, and doesn't offer any benefit as long as you succeed delete

    in the destructor. Another thing that the author would explain in this chapter is why you can mutate a resource owned smart_ptr cache

    by having only a constant object smart_ptr

    inside a method const

    that will describe operator overloading (and most high-level Java and Python programmers throw away the book at this point;)).

    It is also a complex issue in general. First of all, Bjarne Stroustup's books are viewed primarily as teaching materials or guides. So when teaching newbies, should we go to smart pointers or teach raw pointers first? Should we use the standard library immediately, or leave it for the last sections? C ++ 14 from the beginning or "C +" from the beginning? Who knows? There is also a problem known as "overuse of smart pointers" especially shared_ptr

    .

  • Why not just have a mutable cache c member if you're going to allocate and initialize c in the constructor anyway?

    What is described in 16.2.9.3 is correct?

    And adding a layer of indirection here is an alternate solution (showing “no one size fits all”) and demonstrating this awesome quote:

    All problems in computer science can be solved by another level of indirection, except for the problem of too many layers of indirection. - David J. Wheeler Strike>

    Not. As explained by user xan, 16.9.3 refers to multiple mutable members, while one mutable

    struct

    will provide some separation of concerns.

Hope you enjoy reading!

+1


source







All Articles