Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Physical constness of a class

Tags:

c++

I'm studying "The C++ Programming Language" from Bjarne Stroustrup and he talks about logical and physical constness of a class.

The example of logical constness is something like:

class A {
    int m;
    void func() const { m++; } //forbidden
}

It's possible to bypass this with a cast, like:

class A {
    int m;
    void func() const { (A*) this)->m++; } //allowed
}

In his words, logical constness is

"an object that appears constant to its users."

and physical constness is

"stored in read-only memory"

As a note he says that

physical constness may be enforced by placement of an object in read-only memory only for classes without constructors

I didn't quite understand this statement. Could someone provide an explanation on how to enforce physical constness and why it does not work if the class has a constructor?

like image 742
kunigami Avatar asked Feb 28 '10 22:02

kunigami


2 Answers

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.

like image 69
AnT Avatar answered Sep 26 '22 02:09

AnT


The constructor writes into the object as part of the construction process, but this doesn't work if the memory for the object is readonly.

Physical const only works for objects that can be initialized at compile time, and thus have no need for a constructor.

The creation of readonly memory is not something you can do explicitly in C++, but many compilers have extensions that allow you to do this, and most modern compilers will put code and some data in readonly memory if the CPU architecture allows for this.

In MSVC, you can force an initialized global variable to be stored in read-only memory by placing it in a section marked as read but not write like this

#pragma section("rosec",read)
__declspec(allocate("rosec")) int j = 0; // this will be in a readonly data segment.
like image 21
John Knoeller Avatar answered Sep 25 '22 02:09

John Knoeller