Difference Between Logical and Physical Const-Ness

difference between logical and physical const-ness

Scott Meyers, Effective C++, Item 3:

Use const whenever possible

has an excellent discussion (with examples) on this topic. Its hard to write better than Scott!

Note also that physical-constness is also known as bitwise-constness.

logic constant and physical constness

These are not standard terms, but I think that "physically constant" would mean a constant object, and "logically constant" would mean a constant reference or pointer.

In the first case, the object is declared constant, Object const object;, or is a literal constant such as 1 or "hello", and must never be modified; it is undefined behaviour to do so. The object might be placed in read-only memory to enforce its constancy.

In the second case, the pointer or reference is declared to refer to a constant object, Object const * pointer; or Object const & reference;, but the object itself is not necessarily constant. The pointer or reference cannot be used to modify the object (unless subverted with const_cast), but the object may change by other means if it isn't "physically" constant itself.

Physical constness of a class

You received several answers already, but I believe most of them (if not all) miss the point.

To better understand the situation with constness in C++ (and also involve the concepts mentioned in the other answers) let's consider not two, but three possible levels of constness that can be encountered in a C++ program

  1. Hardware/OS level physical constness
  2. Language level physical constness
  3. Logical constness (also, of course, language level)

Hardware/OS level physical constness is the physical constness that other answers seem to be describing. It takes place when the object is placed in memory protected from being written: read-only (RO) memory. The protection can be implemented by hardware-provided means or by OS-provided means or by both. However, the C++ language itself does not separate this kind of constness into a distinctive category. The C++ language does not concern itself with such low-level matters. When the notion of physical constness arises in C++ context, it is usually referring to the next kind of constness.

Language level physical constness. This constness takes place simply when you declare an object as const. The following objects are physical constants from the point of view of C++ language

const double d = 5;
const int i = 42;
const std::string str = "Hello World!";
const MyClass c;

Note, that it doesn't really matter whether these object are really placed in RO memory or not. The language says that any attempts to modify these objects will result in Undefined Behavior (UB), regardless of whether the memory is RO or not. Note also, that if you attempt to modify these objects, the manifestations of that UB are not limited to a mere program crash (if they are really in RO memory). For example, the compiler is free to assume that these objects never change and can optimize the code under that assumption, eliminating access to these objects in situations when it appears to be unnecessary. Because of this, even if the memory occupied by these objects is writeable and even if you manage to "successfully" modify them somehow, your code might still behave as if your modifications never took place. UB is UB. Anything can happen.

From the language point of view this kind of constness is, of course, intended to include the 1st kind.

Finally, to Logical constness. Logical constness in C++ is the constness of so called acces path to the object. Access path is the reference or the pointer that allow you to access to existing object indirectly. Consider this declaration

const MyClass* pc;

This is a pointer to const MyClass type. Note, however: it doesn't really mean that the actual object this pointer is pointing to is a constant. It just let's you "see" it as a constant. The object can easily be either a constant

const MyClass c;
pc = &c;

or it might be a non-constant

MyClass nc;
pc = &nc;

In other words, having just that pointer p you have a constant accss path to some object of type MyClass. You don't know and (normally) don't need to know whether that object is really a constant. Since the access path that was given to you is constant, you have to treat that object as a constant. Of course, if you somehow know that the object on the other end of that access path is not a constant, you can legally cast away the constness of the access path

MyClass* p = const_cast<MyClass*>(pc);

and perform modifying operations on the object (of course, in general case it is not a good programming practice, but it has its valid uses). If the object on the other end of the path turns out to be a constant after all, the behavior will be undefined for the reasons described above.

Note, that the example in the original post is talking about exactly that. When you declare a method of class A as const, it simply means that the implicit this parameter passed to that method will have type const A*, i.e. it will provide a constant access path to the A object. This is what I described above as logical constness. Note, again, that if the object was declared as, say, const A a;, it is a language-level physical constant and modifying it as shown in the example is illegal, regardless of whether the object is residing in RO memory or not.

Now, to provide one final illustration of the above, consider the follwing declaration

const MyClass* const* const* const p = /* whatever */;

This declaration has 4 const qualifiers in it. One of these qualifiers has a major qualitative diference from the others. It is the rightmost one. The rightmost const declares the physical constness of object p (constness of the pointer itself), while the remaining const qualifiers declare logical constness of the objects they will be pointing to (constness of the access path).

Again, I believe that in his book Stroustrup meant to talk about the distinction between the 2nd and 3rd concepts of constness, not about the 1st, since the C++ language doesn't really separate 1st from the 2nd. Note, that the example says that the modification by casting away constness is "allowed", while the language specification clearly says that modifying the constants of the 2nd kind by this approach is immediately illegal.

Which methods should be const?

Neither of those three.

Remember that C++ const means logical constant, not physical constant.

Naturally you may only modify mutable members in case the object is declared constant, but aside from that you only have to preserve the logical state.

So, does your method change the logical state of the object?

Mark a member function as const when it is conceptually not

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.

Should I declare any method that can be const a const method

A non-const method cannot be called through a pointer to a constant object. So if method can be const, not declaring it const would impose artificial limits on its use.

Apart from that, making a method const is an important semantic detail to give the user a feeling of the effect that could be expected from calling it.

What is the use of const in the given function when the member variables are changed by the function?

methods not marked const cannot be called on a const object (or ref or pointer to a const object).

StatDemo sd;
StatDemo const & sdr = sd;
sdr.get(x); // error because getx isn't marked const

However, that means that all the data members accessed from within a method marked const are also const, so you cannot change them (without playing tricks).

That's why your setx won't compile -- x is const within those methods.

Modifying const object through pointer obtained during construction

const enforces "bitwise constness", but what you usually want is "logical constness".

In the case of an object that contains a pointer, this means that a const member function can't modify the pointer itself, but can modify what the pointer refers to. In other words, these examples are well formed, but have undefined behavior.

To get logical constness, you 1) use mutable (or sometimes const_cast) to allow modification of members that don't affect the object's logical state (e.g., cached values/memoization), and 2) generally have to manually enforce not writing to data through a pointer (but if it's an owning pointer, that ownership should probably be delegated to an object that only manages ownership of that data, in which case making it const should normally prevent writing to the data it owns).

As far as the specific detail of having a non-const pointer pointing to data that might itself have been const modified, well, you're basically just getting a (persistent) version of roughly the same thing that const_cast is typically used to do: get non-const access to data to which you'd otherwise only have a const pointer. It's up to you to ensure that you only use this in ways that doesn't cause a problem (but just having and/or writing through that pointer doesn't, in itself, necessarily lead to a problem).

In other words, what we have here are two separate pointers to some data. this lets you access an object's data. In a const member function, you can only read (not) write data via this, unless (as noted above) it's marked mutable. In this case, you're saving a second pointer to the same data. Since there's nothing to mark that as a pointer to const, it's not, so you get non-const access to the data it points at.

Best practice when accessing a non-const method of a member pointer from a const-method

is somehow not a true const function

well, it depends what you want to express.

  1. if A's state logically depends on b's state (this is the implication I get from your question)

    • simplest solution is to give A a member of type B, instead of using either a pointer or a reference. Then you have a const B in your const method, and this expresses your requirement directly

    • next simplest is to just declare the member as const B *b; in the first place - obviously this doesn't work if you also need non-const methods of A to call non-const methods on b.

    • if you need the indirection, another possibility is to use a smart pointer class (I'm not sure if a suitable one already exists) with operator overloads like

       B* operator->();
      const B* operator->() const;

      which would give the guarantee you want

  2. if A's state does not logically depend on b's.

    In this case you don't have a problem: mutating b in a const method is fine, since A's logical state doesn't change. This might often be the case where you have a pointer to eg. a logger, which would otherwise need lots of mutable members.

Allow const member function to edit some member variable using mutable

I would mark segment_length as const and mark norm_of_line_cashed* as mutable.

This is following the concept of logical constness rather than bitwise or physical constness. You are only modifying internal state which won't be visible to the outside world, so logical constness is preserved even though you are technically modifying your class. This is exactly what mutable is made for and this seems like a solid design choice.

One note: you might want to have some bool variable (or std::experimental::optional) to keep track of whether you have a value cached rather than relying on a flag values which are fragile and can lead to headaches in the future.

*maybe you meant "cached".



Related Topics



Leave a reply



Submit