`
jubincn
  • 浏览: 242764 次
  • 性别: Icon_minigender_1
  • 来自: 宁波
文章分类
社区版块
存档分类
最新评论

C++中const的使用

 
阅读更多

简介:

尽管学习和使用C++是件困难的事情,但C++提供了很多优秀的feature,如本文要讨论的const关键字。

因为违反const规则会触发编译时错误。尽管有些人认为使用const关键字是多此一举,但我认为const关键字还是很有用的。

Const的多种用途

和C++中大多数关键字一样,const在不同的语境下也有不同的用途。使用const关键字声明的变量在初始化后就不能再修改了,如下所示:

使用const变量来代替宏常量,既能带来编译时错误检测的好处,还能为调试器带来更多帮助。在调试时,const变量也有symbol与其对应。

const关键字用来修饰指针时会产生更多的变化,主要的区别在于“指针自身是const”还是“指针指向的数据是const”,例如下面的代码:


有个简单的原则来对两者进行区别,即*号的位置。如果const在*之前,那么“指针指向的数据是const的”;如果const在*之后,那么“指针自身是const的”。更简单的方法是:const在*之前,即const在*p之前,用于限定数据*p,因此“指针指向的数据是const的”;const在*之后,即const在p之前,用于限定p,因此“指针自身是const的”。

Const用于限定指针

非const指针可以用于初始化const指针,这意味着不能通过const指针来修改那段内存,但不妨碍其他指针去修改那段内存;不能用const指针初始化或赋值给const指针。

&运算符用于const int时,返回指可赋给const int*;用于int时,返回值可赋给int。

使用强制类型转换可以使const指针赋值给非const指针,但这样做的结果取决于编译器,很容易出问题,例如:
我的编译器成功编译了这段程序,但运行结果是:打印了两个4,而不是4和3。为什么会这样呢?看下汇编代码就清楚了:
关键在于Push 4。编译器并没有将x的值取出放到count的参数中,而是直接将常量4放到cout的参数中。

Const_cast操作符

我觉得这个操作符弊大于利,就不翻译了,感兴趣的看原文吧。

Const变量的存储和字符串常量

使用字符串常量初始化指针在C++中是很常见的。在这个过程中,编译器将为字符串常量分配静态的存储空间,在字符串结尾处添加一个null,最后将这块内存的地址作为返回值赋值给指针。字符串常量的类型为const char数组。

在下面的例子中,编译器首先自动在字符串"Hello world"后加一个null,然后为这个新字符串分配一个静态存储空间--这是一个const char数组,最后将这个数组的首地址赋szMyString。我们将通过szMyString来修改这个字符串,这是很不好的:C++标准并没有规定字符串常量的存储位置,你可能会修改一些不能修改的内存,从而使程序崩溃。
如果你留心,会注意到在C++类型系统的标准中是不允许将const地址赋值给一个非const的指针。因为字符串常量是一个“const字符串数组”,因此下面的这行代码是有问题的:
这段代码当然是合法的,并且这是一种很常见的用法。这似乎和标准的C++语法不一致,实际上,在C/C++中存在很多这样的不一致现象。如果强制使用新的标准,那么很多遗留的C/C++代码将不能使用。
C++标准中对于字符串常量的说明:
  _lex.ccon_) surrounded by double quotes, optionally beginning with the
  letter L, as in "..." or L"...".  A string literal that does not begin
  with  L  is  an  ordinary string literal, also referred to as a narrow
  string literal.  An ordinary string literal has type "array of n const
  char"  and  static storage duration (_basic.stc_), where n is the size
  of the string as defined below, and  is  initialized  with  the  given

1 A  string  literal  is  a  sequence  of  characters  (as  defined   in
  characters.   A string literal that begins with L, such as L"asdf", is
  a wide string literal.  A wide string literal has  type  "array  of  n
  const wchar_t" and has static storage duration, where n is the size of
  the string as defined below, and is initialized with the given charac-
  ters.

2 Whether  all  string  literals  are  distinct  (that is, are stored in
  nonoverlapping objects)  is  implementation-defined.   The  effect  of
  attempting to modify a string literal is undefined.

注意在C++标准的第二条中,“字符串是否必须distinct”取决于具体实现”。也就是说,多个相同的字符串常量声明可以存储在同一个位置。这个可以用下面的代码来验证:


我的输出结果为:
szFirst (Literal String) is at 4266616, szSecond (Literal String) is at 4266616

Const和数据隐藏

当你想使类中的private变量被外部进行只读访问时,可以使用const关键字来实现。
例如下面这个类:
现在,我想方便地打印m_szName,可以这样实现:
现在我可以使用Person::PrintName()方法来打印m_szName。但这种设计有个问题,即类Person与终端紧耦合在一起,其他类则无法使用m_szName。
我们可以使用这样的设计来解耦合:
现在打印m_szName的代码变为:
我想细心的你会发现这里的问题:szTheName的内存并没有被delete,因此产生了内存泄露。我们为什么不像下面的代码这样,只返回一个指针?
尽管这样使打印m_szName的代码缩为两行:
但这样会使private的m_szName暴露出来,并可能被其他程序修改,从而产生混乱。
下面使用const关键字,使GetName()的返回类型为const char*
因此,我们需要修改MyBuggyPrint()函数。此外,我们应该尽可能地使用const,本着这个原则,代码继续修改:
const函数的声明比较特别,是将const关键字放在函数声明的后面。const关键字用于修饰函数指此函数不仅不会修改数据,而且也不会调用其他非const函数。const对象和非const对象都可以调用const函数,但const对象不能调用非const函数,因为非const函数会修改const对象。
下面是修改后的PrintName()函数:
在上面的PrintPerson中,Person对象被声明为const的,所以可以调用const函数,否则会产生编译错误:

Mutable关键字

有时在const函数中进行有限的修改操作时必要的,比如实现cache时这是很有用的一种方法。下面这段函数为了实现cache的效果,不得不使用了const_cast运算符。前面说过,这是个很危险的运算符,我们要尽量避免使用。
mutable关键字可以很好地解决这个问题。即使在const函数中,mutable变量也可以被修改:



原文地址:

注:我的翻译为简单意译,我只翻译了自己认为值得关注的地方,因此和原文还是有一定出入的。

原文如下:

Const Correctness in C++

Introduction

A popularUSENETjoke goes:
In C, you merely shoot yourself in the foot.

In C++, you accidentally create a dozen instances of yourself and shoot them all in the foot. Providing emergency medical care is impossible, because you can't tell which are bitwise copies and which are just pointing at others and saying, "That's me, over there."

While it is true that using the object-oriented features of C++ requires more thought (and hence, more opportunities to make mistakes), the language provides features that can help you create more robust and bug-free applications. One of these features isconst, the use of which I will address in this article.

Used properly with classes,constaugments data-hiding and encapsulation to provide full compile-time safety; violations ofconstcause compile-time errors, which can save you a lot of grief (from side-effects and other accidental modifications of data). Some C++ programmers believeconst-correctness is a waste of time. I disagree - while it takes time to useconst, the benefits almost always outweigh the time spent debugging. Furthermore, usingconstrequires you to think about your code and its possible applications in more detail, which is a good thing. When you get used to writingconst-correctly, it takes less time - this is a sign that you have achieved a state of enlightenment. Hopefully this article will help put you on the path of eternal bliss.

A Constant Variable?

If the concept of a constant variable seems paradoxical to you, keep in mind that in programming terms, "variable" means any named quantity, whether or not it varies.

The Many Faces of Const

Like most keywords in C++, theconstmodifier has many shades of meaning, depending on context. Used to modify variables,const(not surprisingly) makes it illegal to modify the variable after its initialization. For example:
int  x = 4;        // a normal variable that can be modified
x = 10;            // legal

const int x = 2;   // const var can be initialized, not modified thereafter
x = 10;            // error - cannot modify const variable

Thus,constcan replace the use of the#defineto give names to manifest constants. Since preprocessor macros don't provide strong compile-time type checking, it is better to useconstthan#define. Moreover, some debugging environments will display the symbol which corresponds to aconstvalue, but for#defineconstants, they will only display the value.

Theconstkeyword is more involved when used with pointers. A pointer is itself a variable which holds a memory address of another variable - it can be used as a "handle" to the variable whose address it holds. Note that there is a difference between "a read-only handle to a changeable variable" and a "changeable handle to a read-only variable".


const int x;      // constant int
x = 2;            // illegal - can't modify x

const int* pX;    // changeable pointer to constant int
*pX = 3;          // illegal -  can't use pX to modify an int
pX = &someOtherIntVar;      // legal - pX can point somewhere else

int* const pY;              // constant pointer to changeable int
*pY = 4;                    // legal - can use pY to modify an int
pY = &someOtherIntVar;      // illegal - can't make pY point anywhere else

const int* const pZ;        // const pointer to const int
*pZ = 5;                    // illegal - can't use pZ to modify an int
pZ = &someOtherIntVar;      // illegal - can't make pZ point anywhere else


Const With Pointers and Type-Casting

A pointer to a const object can be initialized with a pointer to an object that is not const, but not vice versa.
int y;
const int* pConstY = &y;  // legal - but can't use pConstY to modify y
int* pMutableY = &y;      // legal - can use pMutableY to modify y
*pMutableY = 42;

In the above code, all you're really saying is that you can't usepConstYas a handle to modify the data it points to. Ifyis notconst, then you can safely modifyyvia another pointer,pMutableYfor instance.Pointing atywith aconst int*does not makeyconst, it just means that you can't changeyusing that pointer. Ifyisconst, however, forcing the compiler to let you mess with its value can yield strange results. Although you should never write code that does this, you can play tricks on the compiler and try to modifyconstdata. All you need to do is somehow put the address of theconst intinto a normalint*that you can use to modify theconst int.

C++ does not allow you to circumventconsteasily because the assignment operator can't be used to put the contents of aconst int*into a normalint*without explicit casts.C++ does not supply a standard conversion from aconsttype to a type that is notconst. However, any sort of conversion can be specified with explicit type casts (including unsafe conversions). Thus, the type-system in C++ generally will not allow you to put the address ofconstdata into a pointer to non-constdata.

For example, try to put the address ofx, which isconst, into a normalint*so you can use it to modify the data:


const int x;             // x cannot be modified

const int* pX = &x;      // pX is the address of a const int
                         // and can't be used to change an int

*pX = 4;                 // illegal - can't use pX to change an int

int* pInt;       // address of normal int
pInt = pX;       // illegal - cannot convert from const int* to int*

Nor will compiler let you take the address of aconstvariable and store it in a pointer to non-constdata using the address-of operator (&), for the same reason:
int *pInt;   // address of a normal int
pInt = &x;   // illegal - cannot convert from const int* to int*

The address-of operator returns a pointer to the variable; if the variable is aconst int, it returns aconst int*. If the variable is anint,&returns anint*. C++ makes it difficult to get a pointer to this data which can be used to modify it.

Theconstkeyword can't keep you from purposely shooting yourself in the foot. Using explicit type-casting, you can freely blow off your entire leg, because while the compiler helps prevent accidental errors, it lets you make errors on purpose. Casting allows you to "pretend" that a variable is a different type. For example, C programmers learn early on that the result of dividing an integer by an integer is always an integer:


int x = 37;
int y = 8;

double quotient = x / y;   // classic mistake, result is rounded to an int
cout << quotient;          // prints " 4.000000" 
double quotient = (double)x/y;   // cast result as double so it's not rounded
cout << quotient;          // prints "4.625000"

With casting, you can force the compiler to let you put the address of aconst intvariable into a normalint*. Remember thatconst int*andint*are, in fact, separate types. So you can cast from aconst int*to a normalint*and use the pointer to try and modify data. The result, however, is undefined. The compiler is free to store constants wherever it wants (including non-writeable memory), and if you trick the compiler into letting you try to modify the constant, the result is undefined. This means that it might work, it might do nothing, or it might crash your program.

The following code is a good illustration of how to mess yourself up with forced casting:


const int x = 4;           // x is const, it can't be modified
const int* pX = &x;        // you can't modify x through the pX pointer

cout << x << endl;         // prints "4"

int* pX2 = (int *)pX;      // explicitly cast pX as an int*
*pX2 = 3;                  // result is undefined

cout << x << endl;        // who knows what it prints?

On my system using , this code compiles and runs without crashing, but thexdoes not appear to be changed by the second assignment; it outputs '4' both times.

However, when you look at it more closely, strange things are happening. When you run the code, the output (fromcoutorprintf) seems to show thatxdoesn't change in the second assignment. But when you step through the code, the debugger shows you thatxdoes, in fact, change. So what is happening? Ifxchanges, then why doesn't the output statement reflect this change?

Often in such bizarre situations, it is a good idea to look at the assembler code that was produced. In Visual C++, compile with the/Fa"filename.asm"option to output the assembler with the corresponding lines of code into a file so you can look at it. Don't panic if you don't know much about assembler - if you know how arguments are pushed onto the stack, it's really quite easy to see what's happening.


ASSEMBLER OUTPUT                       C++ CODE
Mov   eax, DWORD PTR _pX$[ebp]         int* pX2 = (int *)pX;
Mov   DWORD PTR _pXX$[ebp], eax
Mov   eax, DWORD PTR _pXX$[ebp]        *pX2 = 3;
Mov   DWORD PTR [eax], 3
Push  OFFSET FLAT:?endl@@.........     cout << x << endl;
Push  4

The important line is "Push 4". The assembler code shows that instead of pushing the value ofxontocout's stack frame, it pushes the literal constant 4 instead. The compiler assumes that since you declaredxasconstand initialized it as 4, it is free to optimize by pushing the literal constant 4 onto the stack rather than having to dereferencexto get its value. This is a valid optmization, and happens in Visual C++ even with all optimization turned off. This code would work fine if we did not declarexasconst. We could use aconst int*to point at a non-const int, and have no trouble.

The Const_cast Operator

The above example is indicative of bad C++ casting manners. Another way to write functionally equivalent code is to use theconst_castoperator to remove theconst-ness from theconst int*. The result is the same:


const int x = 4;      // x is const, it can't be modified
const int* pX = &x;   // you can't modify x through the pX pointer

cout << x << endl;    // prints "4"

int* pX2 = const_cast < int* > (pX);   // explicitly cast pX as non-const

*pX2 = 3;           // result is undefined
cout << x << endl;   // who knows what it prints?

Althought this is a naughty example, it's a good idea to use theconst_castoperator. Theconst_castoperator is more specific than normal type-casts because it can only be used to remove theconst-ness of a variable, and trying to change its type in other ways is a compile error. For instance, say that you changedxin the old-style cast version of the above example to andoubleand changedpXtodouble*. The code would still compile, butpX2would be treating it as anint. It might not cause a problem (becauseints anddoubles are somewhat similar), but the code would certainly be confusing. Also, if you were using user-defined classes instead of numeric types, the code would still compile, but it would almost certainly crash your program. If you useconst_cast, you can be sure that the compiler will only let you change theconst-ness of a variable, and never its type.

Const Storage and String Literals

Another example of using pointers to play around withconststorage is when you try to use achar*to modify a string literal. In C++, the compiler allows the use of string literals to initialize character arrays. A string literal consists of zero or more characters surrounded by double quotation marks ("). A string literal represents a sequence of characters that, taken together, form a null-terminated string. The compiler creates static storage space for the string, null-terminates it, and puts the address of this space into thechar*variable. The type of a literal string is an array ofconst chars.

The C++ standard (sectionlex.string) states:


1 A  string  literal  is  a  sequence  of  characters  (as  defined   in
  _lex.ccon_) surrounded by double quotes, optionally beginning with the
  letter L, as in "..." or L"...".  A string literal that does not begin
  with  L  is  an  ordinary string literal, also referred to as a narrow
  string literal.  An ordinary string literal has type "array of n const
  char"  and  static storage duration (_basic.stc_), where n is the size
  of the string as defined below, and  is  initialized  with  the  given
  characters.   A string literal that begins with L, such as L"asdf", is
  a wide string literal.  A wide string literal has  type  "array  of  n
  const wchar_t" and has static storage duration, where n is the size of
  the string as defined below, and is initialized with the given charac-
  ters.

2 Whether  all  string  literals  are  distinct  (that is, are stored in
  nonoverlapping objects)  is  implementation-defined.   The  effect  of
  attempting to modify a string literal is undefined.

In the following example, the compiler automatically puts a null-character at the end of the literal string of characters "Hello world". It then creates a storage space for the resulting string - this is an array of const chars. Then it puts the starting address of this array into theszMyStringvariable. We will try to modify this string (wherever it is stored) by accessing it via an index intoszMyString. This is a Bad Thing; the standard does not say where the compiler puts literal strings. They can go anywhere, possibly in some place in memory that you shouldn't be modifying.
char* szMyString = "Hello world.";   
szMyString[3] = 'q';         // undefined, modifying static buffer!!!

InJames Coplien's book,Advanced C++ Programming Styles & Idioms, I came across the following code (p. 400):
char *const a = "example 1";   // a const pointer to (he claims) non-const data
a[8] = '2';         // Coplien says this is OK, but it's actually undefined

Both of these examples happen to work on my system, but you shouldn't rely on this kind of code to function correctly. Whether or not the literal strings you point to are explicitly declaredconst, you shouldn't try to modify them, because the standard states that they are in factconst.

If you've been paying attention, you'll remember that the type-system in C++ will not allow you to put the address ofconstdata into a pointer to non-constdata without using explicit type casts, because there is no standard conversion between const types and types that are not const. Example:


   const char constArray[] = { 'H', 'e', 'l', 'l', 'o', '\0' };
   char nonConstArray[] = { 'H', 'e', 'l', 'l', 'o', '\0' };
   char* pArray = constArray;            // illegal
   char* pArray = nonConstArray;         // legal

If, as the standard says, an ordinary string literal has type "array ofnconst char", then the following line of code should cause an error just like the above example:
   // should be illegal - converts array of 6 const char to char*
   char* pArray = "Hello";

Of course, this code is a common idiom and it's perfectly legal. This appears to be an inconsistency in the language standard. A lot of these inconsistencies exist because older C and C++ code would break if the standard were strictly consistent. The standards people are afraid to break old code, because it would mean a decrease in the popularity of the language.

Notice item 2 in the above quote from the language standard: literal strings don't have to be distinct. This means that it is legal for implementations to use string pooling, where all equal string literals are stored at the same place. For example, the help in Visual C++ states:

"The/GFoption causes the compiler to pool strings and place them in read-only memory. By placing the strings in read-only memory, the operating system does not need to swap that portion of memory. Instead, it can read the strings back from the image file. Strings placed in read-only memory cannot be modified; if you try to modify them, you will see an Application Error dialog box. The/GFoption is comparable to the/Gfoption, except that/Gfdoes not place the strings in read-only memory. When using the/Gfoption, your program must not write over pooled strings. Also, if you use identical strings to allocate string buffers, the/Gfoption pools the strings. Thus, what was intended as multiple pointers to multiple buffers ends up as multiple pointers to a single buffer."
To test this, you can write a simple program as follows:
#include <stdio.h>

int main()
{
   char* szFirst = "Literal String";
   char* szSecond = "Literal String";

   szFirst[3] = 'q';
   printf("szFirst (%s) is at %d, szSecond (%s) is at %d\n",
         szFirst, szFirst, szSecond, szSecond);

   return 0;
}

On my system, this program outputs:
szFirst (Litqral String) is at 4266616, szSecond (Litqral String) is at 4266616
Sure enough. Although there was only one change, since string pooling was activated, bothchar*variables pointed to the same buffer. The output reflects this.

Const and Data-Hiding

It is often useful to useconstvariables when you have private data in a class, but you want to easily access the data outside of the class without changing it. For example:
class Person
{
   public:
      Person(char* szNewName)
      {
         // make a copy of the string
         m_szName = _strdup(szNewName);
      };

      ~Person() { delete[] m_szName; };

   private:
      
      char* m_szName; 
};

Now, what if I wanted to easily print out the person's name? I could do the following:
class Person
{
   public:
      Person(char* szNewName)
      {
         // make a copy of the string
         m_szName = _strdup(szNewName);
      };

      ~Person() { delete[] m_szName; };

      void PrintName()
      {
         cout << m_szName << endl;
      };

   private:
      
      char* m_szName; 
};

Now I can callPerson::PrintName()and it will print the name out to the console. There is a design problem with this code, however. It builds dependencies on theiostreamlibraries and the console I/O paradigm right into thePersonclass. Since aPersoninherently has nothing to do with console I/O, one shouldn't tie the class to it. What if you want to print out the name in a Windows or X-Windows application? You'd need to change your class, and that reeks.

So, we can do something like the following:


class Person
{
   public:
      Person(char* szNewName)
      {
         // make a copy of the string
         m_szName = _strdup(szNewName);
      };

      ~Person() { delete[] m_szName; };

      void GetName(char *szBuf, const size_t nBufLen)
      {
         // ensure null termination in the copy
         strncpy(szBuf, m_szName, nBufLen - 1);
      };
   private:
      char* m_szName; 
};

Now we can print the name out by doing something like this:
Person P("Fred Jones");
char szTheName = new char[256];
P.GetName(szTheName, 256);
cout << szTheName << endl;

Wow, three lines of code just to print out a name. And I bet you didn't even notice that we forgot to delete the dynamic memory forszTheName! There must be a better way to do this. Why don't we just return a pointer to the string?
class Person
{
   public:
      Person(char* szNewName)
      {
         // make a copy of the string
         m_szName = _strdup(szNewName);
      };

      ~Person() { delete[] m_szName; };

      char* GetName()
      {
         return m_szName;
      };

   private:
      
      char* m_szName; 
};

With this, you can print out the code in one line:
Person P("Fred Jones");
cout << P.GetName() << endl;

Much shorter, but as you may have noticed, them_szNamevariable isprivateinside thePersonclass! What's the point of declaring it asprivateif you're going to pass out non-constpointers to it? What if you wrote a buggy print function that modified what it was printing?
// this function overwrites szString 
// (which may have held the address of dynamically allocated memory)
void MyBuggyPrint(char* szString)
{
   // make a copy of the string and print out the copy
   szString = _strdup(szString);

   cout << szString << endl;
   
   free (szString);
}

Person P("Fred Jones");
MyBuggyPrint(P.GetName());

TheMyBuggyPrintfunction makes a new string, puts the new string's address in its first parameter, prints it, then deletes it. This results in two related problems. We pass in a pointer to the string data that was allocated in thePersonconstructor, the pointer gets set to the location of the string copy, which then gets deleted. SoP.m_szNameis left pointing to garbage. Second, since you lose the original location of the string pointed to bym_szName, you never free the string, so it's a memory leak.

Fortunately, theconstkeyword comes in handy in situations like this. At this point, I'm sure some readers will object that if you write your code correctly, you won't need to protect yourself from your own mistakes - "You can either buy leaky pens and wear a pocket protector, or just buy pens that don't leak, period." While I agree with this philosophy, it is important to remember that when you're writing code, you're not buying pens - you're manufacturing pens for other people to stick in their pockets. Usingconsthelps in manufacturing quality pens that don't leak.


class Person
{
   public:
      Person(char* szNewName)
      {
         // make a copy of the string
         m_szName = _strdup(szNewName);
      };

      ~Person() { delete[] m_szName; };

      const char* const GetName()
      {
         return m_szName;
      };

   private:
      
      char* m_szName; 
};

Person P("Fred Jones");

MyBuggyPrint(P.GetName());   // error! Can't convert const char* const to char*


This time, we're returning aconst char* constfrom the class, which means that you can't change the pointer to point somewhere else, and you can't modify what the pointer points to. Now your code won't even compile, because yourMyBuggyPrintfunction expects achar*.

This brings up an interesting point. If you wrote your code this way, you'd have to go back and rewrite yourMyBuggyPrintfunction to take aconst char* const(hopefully fixing it in the process). This is a pretty inefficient way to code, so remember that you should useconstas you go - don't try to make everythingconstcorrect after the fact. As you're writing a function likeMyBuggyPrint, you should think "Hmmm...do I need to modify what the pointer points to? No...do I need to point the pointer somewhere else? No...so I will use aconst char* constargument." Once you start thinking like this, it's easy to do, and it will keep you honest; once you start usingconstcorrectness, you have to use it everywhere.

With this philosophy, we could further modify the above example by having thePersonconstructor take aconst char* const, instead of achar*. We could also further modify theGetNamemember function. We can declare it as:


class Person
{
   public:
      Person(char* szNewName)
      {
         // make a copy of the string
         m_szName = _strdup(szNewName);
      };

      ~Person() { delete[] m_szName; };

      const char* const GetName() const
      {
         return m_szName;
      };

   private:
      
      char* m_szName; 
};

Declaring a member function asconsttells the compiler that the member function will not modify the object's data and will not invoke other member functions that are not const. The compiler won't take you at your word; it will check to make sure that you really don't modify the data. You can call aconstmember function for either aconstor a non-constobject, but you can't call a non-constmember function for aconstobject (because it could modify the object).

If we declareGetName()as aconstmember function, then the following code is legal:


void PrintPerson(const Person* const pThePerson)
{
   cout << pThePerson->GetName() << endl;   // OK
}

// a const-reference is simply an alias to a const variable
void PrintPerson2(const Person& thePerson)
{
   cout << thePerson.GetName() << endl;   // OK
}

But if we don't declare it asconst, then the code won't even compile.
void PrintPerson(const Person* const pThePerson)
{
   // error - non-const member function called
   cout << pThePerson->GetName() << endl;
}

void PrintPerson2(const Person& thePerson)
{
   // error - non-const member function called
   cout << thePerson.GetName() << endl;
}

Remember that non-static member functions take as their implicit first parameter a pointer calledthis, which points to a specific instance of the object. Thethispointer is alwaysconst- you cannot makethispoint to anything else (in earlier versions of C++, this was legal).

Aconstmember function inclass Personwould take aconst class Person* const(constpointer toconst Person) as its implicit first argument, whereas a non-constmember function inclass Personwould take aclass Person* const(constpointer to changeable Person) as its first argument.

The Mutable Storage Specifier

What if you wanted to have aconstmember function which did an expensive calculation and returned the result? It would be nice to be able to cache this result and avoid recalculation for subsequent calls to the function. But since it's aconstmember function, you can't store the cached result inside the class, because to do so, you'd have to modify a member variable (thereby violatingconst).

You could make a fakethispointer using explicit casting:


class MyData
{
   public:
      /*
      the first time, do calculation, cache result in m_lCache, and set
      m_bCacheValid to true. In subsequent calls, if m_bCacheValid is true
      then return m_lCache instead of recalculating
      */

      long ExpensiveCalculation() const
      {
         if (false == m_bCacheValid)
         {
            MyData* fakeThis = const_cast<MyData*>this;
            fakeThis->m_bCacheValid = true;
            fakeThis->m_lCache = ::SomeFormula(m_internalData);
         }
         return m_lCache;
      };

      // change internal data and set m_bCacheValid to false to force recalc next time
      void ChangeData()
      {
         m_bCacheValid = false;
         m_internalData = ::SomethingElse();
      };

   private:

      data m_internalData;
      long m_lCache;
      bool m_bCacheValid;
            
};

This works, but it's somewhat ugly and unintuitive. Themutablestorage specifier was added for this reason. Amutablemember variable can be modified even byconstmember functions. Withmutable, you can distinguish between "abstractconst", where the user cannot tell that anything has been changed inside the class, and "concreteconst", where the implementation will not modify anything, žR5od. This caching of results is a perfect example of abstractconst-ness. Anyone calling theconstmember function will not know or care whether the result has been cached or recalculated. For example:
class MyData
{
   public:
      /*
      the first time, do calculation, cache result in m_lCache, and set
      m_bCacheValid to true. In subsequent calls, if m_bCacheValid is true
      then return m_lCache instead of recalculating
      */

      long ExpensiveCalculation() const
      {
         if (false == m_bCacheValid)
         {
            m_bCacheValid = true;
            m_lCache = ::SomeFormula(m_internalData);
         }
         return m_lCache;
      };

      // change data and set m_bCacheValid to false to force recalc next time
      void ChangeData()
      {
      };

   private:

      data m_internalData;
      mutable long m_lCache;
      mutable bool m_bCacheValid;
            
};

References

This paper represents a synthesis and compilation of information from the following sources:
分享到:
评论

相关推荐

    C++中const使用说明

    以下是对`const`在C++中使用的详细说明: 1. **`const`修饰指针** - `const`位于星号左侧:`const int *a` 表示指针`a`指向的变量是常量,即`a`不能改变其指向的`int`型数值。 - `const`位于星号右侧:`int * ...

    C++中const用法全解

    const 在 C++ 中占有重要作用,属于小兵立大功的典型,本文档详细介绍了如何使用 const。 1. const 常量 在 C++ 中,const 可以用来修饰变量,称为常量。例如:const int max = 100; 这种常量有数据类型,编译器...

    c++中const关键字使用详解

    本文将详细介绍const关键字在C++中的使用方法。 一、const基础 首先,const关键字可以用来修饰基本数据类型、指针、类的成员函数等。根据const修饰的位置不同,其作用也有差异。 1. const修饰基本数据类型 对于...

    C++中const用法总结

    ### C++中const用法总结 #### 一、引言 `const` 是 C++ 中一个非常重要的关键字,用于声明常量或指定某些实体不可被修改。熟练掌握 `const` 的使用方法对于编写高质量的 C++ 代码至关重要。本文将详细介绍 `const`...

    2、C++中const变量.docx

    const变量可以在以下场景中使用: * 定义数组时,使用const变量可以防止数组的修改。 * 在函数参数传递中,使用const变量可以防止函数内部修改参数的值。 * 在函数返回值中,使用const变量可以防止返回值被修改。 ...

    C++ 中const总结

    ### C++ 中const总结 #### 一、const变量的基本概念 在C++中,`const`关键字用于声明常量或指定对象的一部分不可更改。例如,`const int bufSize = 512;` 这样的声明意味着`bufSize`是一个整型常量,其值在初始化...

    C++ const应用总结

    在C++编程语言中,`const`关键字是一个非常重要的特性,它用于定义常量和声明不可修改的对象。本文将深入探讨`const`在C++中的各种应用,帮助你更好地理解和运用这一关键概念。 1. 常量声明: `const`关键字用于...

    C中const和C++中const区别

    此外,在C++中,我们可以使用const_cast来强转const属性。例如: #include using namespace std; int main(){ const int a = 10; const int *pa = &a; int* pb = const_cast*&gt;(pa); *pb = 20; cout *pa ; ...

    关于const在C++中的用法

    关于`const`在C++中的用法,是一个深入理解C++编程语言的关键点。`const`关键字在C++中主要用于声明常量或者指定某个变量、指针或成员函数的某些特性不可修改,它增强了代码的安全性和可读性,同时也提高了程序的...

    c++里const用法归纳

    C++中const用法归纳 C++中的const关键字是一种非常灵活的语言特性,它可以用来修饰变量、指针、函数参数、返回值、成员函数等多种类型。正确使用const关键字可以大大改善程序的健壮性和可读性。 1. Const常量 ...

    C++中const关键字详解

    ### C++中const关键字详解 #### 一、引言 在C++编程语言中,`const`关键字是一个非常重要的概念,它被广泛用于各种上下文中,以提高代码的安全性、可读性和效率。本文将详细介绍`const`关键字的用法及其在不同场景...

    C++关于Const使用

    ### C++中Const关键字的理解与应用 #### 一、Const关键字的基本概念 在C++编程语言中,`const`关键字是一种类型限定符,用于声明常量或者指定对象的一部分为不可变。通过`const`关键字,程序员可以明确指出某些...

    c++中const用法详解

    在类中使用`const`关键字可以定义只读数据成员: ```cpp class A { public: const int size; // ... }; // 初始化必须在构造函数的初始化列表中进行 A::A(int sz) : size(sz) {} ``` 此类成员只能在构造函数的...

    c++中const用法总结

    ### C++中const用法详解 #### 一、引言 在C++编程语言中,`const`关键字具有非常重要的作用。它可以帮助开发者确保代码的稳定性与安全性,尤其是在多线程环境中更是如此。本文旨在全面解析`const`在C++中的多种...

    c++中const的使用

    `const`关键字在C++中的灵活应用能够显著提高代码的质量和性能,通过正确使用`const`,可以减少编程错误,提高代码的健壮性和维护性。理解`const`的各种用法和规则对于编写高质量的C++代码至关重要。

    C++中的const限定修饰符

    ### C++中的const限定修饰符 #### 一、引言 在C++编程语言中,`const`关键字是一个非常重要的特性,它可以帮助开发者确保某些数据或对象在其生命周期内的不变性,从而提高代码的安全性和可维护性。本文将详细介绍`...

    C++ const使用详解

    在函数参数中使用 const 关键字可以限定该参数的值不能被修改。例如:`void display(const double& r);` 或者 `void display(const double* r);` 使用 const 限制参数的目的是保证引用的变量的值不被改变。 需要...

    c++中const 的各种用法

    `const`关键字在C++中有着广泛的应用,从简单的变量到复杂的类对象都可以使用它进行修饰。通过合理地利用`const`,不仅可以提高代码的健壮性和可维护性,还可以提高程序的执行效率。希望本文能够帮助读者更好地理解...

    c++中const详解

    C++中的`const`关键字是编程中至关重要的一个特性,它允许我们声明不可修改的变量、常量以及指针和函数。使用`const`能够提高程序的健壮性,通过限制不必要的修改,防止意外的数据变动,同时也有助于编译器进行类型...

Global site tag (gtag.js) - Google Analytics