GotW #6a: Const-Correctness, Part 1

const and mutable have been in C++ for many years. How well do you know what they mean today?

 

Problem

JG Question

1. What is a “shared variable”?

Guru Questions

2. What do const and mutable mean on shared variables?

3. How are const and mutable different in C++98 and C++11?

12 thoughts on “GotW #6a: Const-Correctness, Part 1

  1. @Gregory:

    These instances a, b, are related through their sharing the same counter:

    IntRef a;
    IntRef b(a);
    

    Such related instances are already liable to cause UB through data race when accessible from multiple threads (banned by the “Dem’s De Rules, Part 1 of 2” snippet of the standard in Herb’s talk).

    But specifically, suppose two threads each have their own private std::vector. Thread 1 says:

    vec1.push_back(a)
    

    And symmetrically thread 2 says:

    vec2.push_back(b)
    

    Subsequent calls to push_back on vec1 will potentially call the copy constructor of a again, as the vector may need to reconfigure its internal storage. Similarly for thread 2 and b. As a and b are related, this will cause attempts to increment the same counter, which would be a data race.

    This is banned by the “Dem’s De Rules, Part 2 of 2” snippet of the standard in Herb’s talk, because the counter being modified, indirectly, by push_back, has not been accessed via (directly or indirectly) a non-const argument to push_back (indeed, not by any argument passed to the subsequent call to push_back), and yet is accessible to another thread.

  2. 1. What is a “shared variable”?

    Any variable that has a scope larger than a “single user” — to me that means it can be a class member or a global variable or a variable that has shared ownership amongst multiple points of access in a program even for a single threaded application.

    Any variable that has scope that crosses multiple threads is even more “shared” than that.

    2. What do const and mutable mean on shared variables?

    `const` acts as a contract between provider and client. Declaring that something is a const parameter to a function, tells the clients of that API that the object that they’re handing over will not be used in any but a const manner (i.e. read-only or its const-member-functions employed).

    Casting around the interface you’re providing or using is almost certainly wrong – it violates the contract – and at that point you’re asking for the code to break — if not today, then in the future during maintenance.

    `mutable` means “internally modifiable under const contract conditions.” i.e. for a class A with mutable int b, b can be modified by any of A’s const member functions. It is not a violation of A’s const interface to modify mutable member b.

    I’ve used this commonly to cache values for a class – things that do not affect the state or meaning of A, but only help A do it’s job. e.g. if we have a class that represents a Filename, then I might have mutable members for volume, path, name, extension. As long as the filename as a whole doesn’t change semantics, those members can be updated at the pleasure of Filename – even in via its const APIs.

    Now that we’re dealing with multiple threads, mutable is useful to specify on the synchronization object if it is a member. Whether the internal state of my object is locked or not doesn’t change the semantics of it. So the const interface can remain coherent while internally I take a read-lock and do whatever I need to in order to execute the API-contract for my client.

    3. How are const and mutable different in C++98 and C++11?

    ???

  3. I’m a bit lost trying to put the change of meaning of const into the bigger picture. So, let me ask two counter-questions.

    1) You have a simple struct

    struct A
    {
    std::string first;
    std::string second;
    };

    And function that takes struct A by const-reference.


    void f(A const& a);

    Without any assumptions, is it legal to put parameter a into standard container?

    2) You have following class:

    class IntRef
    {
    public:
    IntRef():ptr{new int(0)} {}

    IntRef(const IntRef& other):ptr(other.ptr)
    {
    increment();
    }

    void increment() const
    {
    std::shared_ptr p = ptr;
    ++*p;
    }
    private:
    std::shared_ptr ptr;
    };

    Is it safe to put object of this class into standard container?

  4. Hans Boehm uses the term “shared variable” throughout all his papers. However, he fails to give a formal definition as far as I can tell. The “Dictionary of Computer Science, Engineering and Technology” defines it as follows:
    “a variable that can be accessed by two or more concurrent program units”
    However, that definition is extremely outdated, so a formal definition is definitely needed!

    Since we don’t have a formal definition of “shared variable”, the only question left
    to answer is the third one and I cannot write a better answer than what is
    already found out the following link:
    http://stackoverflow.com/questions/14127379/does-const-mean-thread-safe-in-c11

  5. I’ll start with const, when used in a parameter list.

    void foo ( const std::string &s ) { /* ... */ }
    

    Nothing in ‘foo’ should change ‘s’; no changing of public members, no calling of non-const member functions, no passing of ‘foo’ to a function that takes a non-const reference.

    Notes:
    1) `const_cast` can be used to get around this
    2) There’s no guarantee that some other thread of execution will not change ‘s’ while foo is executing; the string referred to by ‘s’ may not be const; only foo’s view of ‘s’ is const.

  6. > What is a “shared variable”?

    It’s an English phrase that has no well-defined meaning without some form of context. The first word, “shared” means to allow ownership between multiple parties. The word “variable” refers to the well-defined C++ concept of a variable. Thus the term would represent a variable that is in some way owned by multiple parties.

    The term “shared variable” is not defined by any version of ISO/IEC 14882 Standard for Programming Language C++. Nor is it a term in common, colloquial usage among C++ programmers.

    Therefore, I submit that the term cannot be considered to have a firm definition outside of what the words themselves mean. As such, it means nothing without further context. What are the multiple parties in question who might have ownership of the variable? What kind of ownership is under discussion?

    Furthermore, I would submit that it is not appropriate for Herb to unilaterally declare that this phrase has *any* particular meaning. It’s bad enough that we’ve got Scott Meyers inventing non-standard terms like “Universal References” that ultimately serve to drive them away from proper standard language. We don’t need multiple high-profile C++ people making it difficult for people to effectively communicate by unilaterally inventing terminology for concepts ex nihilo.

  7. @zahirtezcan:

    Beware of that presentation though. The issues are not clear cut. First, 17.6.5.9/3 of the C++11 standard provides an absolute preclusion of bitwise modification of an object through a const argument, irrespective of whether it would be thread-safe to do so because there is internal synchronization so it can safely be made mutable. Possibly 17.6.5.9/7 is intended to permit this, but it is not clear (to me at least) that it qualifies 17.6.5.9/3, nor is is it clear to me what is meant by “implementations” in relation to user provided objects. Secondly, just because a library function taking an object by const reference is guaranteed by 17.6.5.9/3 not to modify the object does not of itself make a read operation made by that function thread safe. If an object is visible to another thread, the read is only thread safe if the other thread does not modify it or there is synchronization. const does not therefore seem to me to mean “thread-safe” in anything other than a significantly qualified sense.

Comments are closed.