Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

Constant Member Functions Are Not Always Constant

3.63/5 (24 votes)
24 May 2012CPOL3 min read 51.8K   194  
In this article, I'm going to explain why the constant member functions aren't always constant, i.e. despite of the fact that a function is declared as constant, it still allows setting or changing data members within the class

Herakleitos

Introduction

Recently, I had a discussion with my friends about constant member functions in C++ (for example void MyClass::foo const {...}) And one of my friends (he is really great at C++) said that if a member function is const-qualified, then we cannot modify any member variables in the scope of this function. Honestly, such a categorical opinion surprised me and I said, "it's not quite right!!" But all the participants in the debate supported my friend's view. This discussion prompted me to write a short article on the topic about how easy it is to modify the data members of your class in a const function of C++

Background

In fact, even one of the most-read books about C++, called "The C++ Programming Language, Third Edition" by Bjarne Stroustrup, says the following:

"10.2.6 Constant Member Functions [class.constmem]

C++
class Date
{
    int d, m, y;
    public :
       int day () const     { return  d; }
       int month () const { return m; }
       int year () const ;
    // ...
};

Note the const after the (empty) argument list in the function declarations. It indicates that these functions do not modify the state of a Date.

Naturally, the compiler will catch accidental attempts to violate this promise. For example:

C++
inline int Date::year () const
{
   return y ++; // error:  attempt to change member value in const function
}

When a const member function is defined outside its class, the const suffix is required:
inline int Date::year () const
{
   return y ; // correct
}

And really, if your code tries to change some data member in a const function (by the above manner), you would get an error. When you develop an application by using Visual C++, you will receive the following message:

       error C3490: <your data member> cannot be modified because it is being accessed through a const object

I don't want to argue with the compiler (and even more so with the genial creator of C++) I only want to show another case ...

From my point of view, the author of the book didn't provide full disclosure of this subject, therefore, many programmers understand this subject insufficiently.

The Solution

I use a slightly different technique than the one showed by B.Stroustrup. My approach here allows you to use an interesting trick. The trick may look quite ugly, but it works ...

C++
class User
{
  public:
	User(unsigned int id, unsigned int age, std::string name) 
		: mID(id) , 
		  mAge(age),
		  mName(name)
	{

	}

	void ChangeMe1 (unsigned int id, unsigned int age, std::string name) const;
	void ChangeMe2 (unsigned int id, unsigned int age, std::string name) const;
	void PrintMyData ();
private:
	unsigned int           mID;
	unsigned int           mAge;
        std::string            mName;
};

//To make changes by the pointer (a synonym on this)
void User::ChangeMe1 (unsigned int id, unsigned int age, std::string name) const
{
	// Here can be used and the old C-style cast to remove the constness, i.e.:
        // User * synonym_of_this =  (User *) this;
        User * synonym_of_this = const_cast<User *>(this);


	synonym_of_this->mID   = id; 
	synonym_of_this->mAge  = age;
	synonym_of_this->mName = name; 
}

//To make changes by creating the reference on this
void User::ChangeMe2 (unsigned int id, unsigned int age, std::string name) const
{
	// Here can be used and the old C-style cast to remove the constness, i.e.:
        // const UserPtr &synonym_of_this =  (User *) this;
        const UserPtr &synonym_of_this = const_cast<User *>(this);


	synonym_of_this->mID   = id; 
	synonym_of_this->mAge  = age;
	synonym_of_this->mName = name;
}

void User::PrintMyData ()
{
	std::cout << "ID = " << this->mID << " Age = " << this->mAge <<  " Name = " << this->mName.c_str() << "\n"; 
}

int main()
{
	User presedent_USA (42,46,"Bill Clinton");
	std::cout << "Now the presedent of USA : " << std::endl;
	presedent_USA.PrintMyData();
	
        std::cout << "\nLet's hold an election..." << std::endl;
	presedent_USA.ChangeMe1(43,54,"George Walker Bush");
	std::cout << "\nNow the presedent of USA : " << std::endl;
	presedent_USA.PrintMyData();

	std::cout << "\nLet's hold an election..." << std::endl;
	presedent_USA.ChangeMe2(44,47,"Barack Hussein Obama 2");
        std::cout << "\nNow the presedent of USA : " << std::endl;
	presedent_USA.PrintMyData();
	std::cout << "\n";

	return 0;
}

As you can be see from the above code example we continue to use the const-qualified function, but after starting the following program:

Image 2

We can see that all members of this class have been changed, i.e. the ChangeMe1, and ChangeMe2 a "const" functions can modify any data members. Apparently the famous greek philosopher Heraclitus was right. In the ChangeMe1 function I used a "workaround" based on accessing data members through a pointer which is a synonym for this. The second solution used in ChangeMe2 is based on creating a reference to this, which behaves also as a synonym for this.

Why is it so important to consider?

  1. The const function can change the data members of class
    (for example, if you deal with scary legacy code and it's necessary to detect the place where some members of a class are changed, then do not ignore the code in the scope of a constant function, because there also may be changes)
  2. If you want to change the data members of a class (for some reason), but it's not possible to change the signature of a method, then you can use the "trick" that I showed. It is important to note that this should be done to resolve problems, but only in very extreme cases. It basically doesn't make any sense to cancel or violate the constancy of constant method, but if you are going to use this "trick", please write detailed comments, to be able to maintain your code. That's all!

History

  • 18nd April 2011: Initial post
  • License

    This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)