c++ const Qualifier

Initialization

We can make a variable unchangeable by defining the variable’s type as const:

const int bufSize = 512; // input buffer size

Because we can’t change the value of a const object after we create it, it must be initialized.

By Default, const Objects Are Local to a File

When a const object is initialized from a compile-time constant, such as in our definition of bufSize:

const int bufSize = 512; // input buffer size

the compiler will usually replace uses of the variable with its corresponding value during compilation. That is, the compiler will generate code using the value 512 in the places that our code uses bufSize.

To substitute the value for the variable, the compiler has to see the variable’s initializer.

When we split a program into multiple files, every file that uses the const must have access to its initializer.

In order to see the initializer, the variable must be defined in every file that wants to use the variable’s value.

To support this usage, yet avoid multiple definitions of the same variable, const variables are defined as local to the file.

When we define a const with the same name in multiple files, it is as if we had written definitions for separate variables in each file.

Sometimes we have a const variable that we want to share across multiple files but whose initializer is not a constant expression.

To define a single instance of a const variable, we use the keyword extern on both its definition and declaration(s):

// file_1.cc defines and initializes a const that is accessible to other files
extern const int bufSize = fcn();
// file_1.h
extern const int bufSize; // same bufSize as defined in file_1.cc

In this program, file_1.cc defines and initializes bufSize. Because this declaration includes an initializer, it is (as usual) a definition.

However, because bufSize is const, we must specify extern in order for bufSize to be used in other files.

The declaration in file_1.h is also extern. In this case, the extern signifies that bufSize is not local to this file and that its definition will occur elsewhere.

Note:
To share a const object among multiple files, you must define the variable as extern.

References to const

As with any other object, we can bind a reference to an object of a const type.

To do so we use a reference to const, which is a reference that refers to a const type.

const int ci = 1024;
const int &r1 = ci; // ok: both reference and underlying object are const
r1 = 42; // error: r1 is a reference to const
int &r2 = ci; // error: non const reference to a const object

Terminology: const Reference is a Reference to const
C++ programmers tend to abbreviate the phrase “reference to const” as “const reference.”

Technically speaking, there are no const references. A reference is not an object, so we cannot make a reference itself const.

Indeed, because there is no way to make a reference refer to a different object, in some sense all references are const.

Two exceptions to the rule that the type of a reference must match the type of the object to which it refers:

  1. We can initialize a reference to const from any expression that can be converted to the type of the reference.

In particular, we can bind a reference to const to a nonconst object, a literal, or a more general expression:

int i = 42;
const int &r1 = i; // we can bind a const int& to a plain int object
const int &r2 = 42; // ok: r1 is a reference to const
const int &r3 = r1 * 2; // ok: r3 is a reference to const
int &r4 = r * 2; // error: r4 is a plain, non const reference

The easiest way to understand this difference in initialization rules is to consider what happens when we bind a reference to an object of a different type:

double dval = 3.14;
const int &ri = dval;

To ensure that the object to which ri is bound is an int, the compiler transforms this code into something like:

const int temp = dval; // create a temporary const int from the double
const int &ri = temp; // bind ri to that temporary

In this case, ri is bound to a temporary object. A temporary object is an unnamed object created by the compiler when it needs a place to store a result from evaluating an expression.

C++ programmers often use the word temporary as an abbreviation for temporary object.

Now consider what could happen if this initialization were allowed but ri was not const:

If ri weren’t const, we could assign to ri. Doing so would change the object to which ri is bound.

That object is a temporary, not dval. The programmer who made ri refer to dval would probably expect that assigning to ri would change dval. After all, why assign to ri unless the intent is to change the object to which ri is bound?

Because binding a reference to a temporary is almost surely not what the programmer intended, the language makes it illegal.

  1. A Reference to const May Refer to an Object That Is Not const

It is important to realize that a reference to const restricts only what we can do through that reference.

int i = 42;
int &r1 = i; // r1 bound to i
const int &r2 = i; // r2 also bound to i; but cannot be used to change i
r1 = 0; // r1 is not const; i is now 0
r2 = 0; // error: r2 is a reference to const

Binding r2 to the (nonconst) int i is legal. However, we cannot use r2 to change i.

Even so, the value in i still might change. We can change i by assigning to it directly, or by assigning to another reference bound to i, such as r1.

Pointers and const

Like a reference to const, a pointer to const may not be used to change the object to which the pointer points.

const double pi = 3.14; // pi is const; its value may not be changed
double *ptr = π // error: ptr is a plain pointer
const double *cptr = π // ok: cptr may point to a double that is const
*cptr = 42; // error: cannot assign to *cptr

There are two exceptions to the rule that the types of a pointer and the object to which it points must match:

  1. We can use a pointer to const to point to a nonconst object

Like a reference to const, a pointer to const says nothing about whether the object to which the pointer points is const.

Defining a pointer as a pointer to const affects only what we can do with the pointer.

It is important to remember that there is no guarantee that an object pointed to by a pointer to const won’t change.

Unlike references, pointers are objects.

Hence, as with any other object type, we can have a pointer that is itself const.

Like any other const object, a const pointer must be initialized, and once initialized, its value (i.e., the address that it holds) may not be changed.

int errNumb = 0;
int *const curErr = &errNumb; // curErr will always point to errNumb
const double pi = 3.14159;
const double *const pip = π // pip is a const pointer to a const object

Top-Level const

We use the term top-level const to indicate that the pointer itself is a const.

When a pointer can point to a const object, we refer to that const as a low-level const.

int i = 0;
int *const p1 = &i; // we can't change the value of p1; const is top-level
const int ci = 42; // we cannot change ci; const is top-level
const int *p2 = &ci; // we can change p2; const is low-level
const int *const p3 = p2; // right-most const is top-level, left-most is not
const int &r = ci; // const in reference types is always low-level

The distinction between top-level and low-level matters when we copy an object.

When we copy an object, top-level consts are ignored:

i = ci; // ok: copying the value of ci; top-level const in ci is ignored
p2 = p3; // ok: pointed-to type matches; top-level const in p3 is ignored

Copying an object doesn’t change the copied object. As a result, it is immaterial whether the object copied from or copied into is const.

On the other hand, low-level const is never ignored.

When we copy an object, both objects must have the same low-level const qualification or there must be a conversion between the types of the two objects.

In general, we can convert a nonconst to const but not the other way round:

int *p = p3; // error: p3 has a low-level const but p doesn't
p2 = p3; // ok: p2 has the same low-level const qualification as p3
p2 = &i; // ok: we can convert int* to const int*
int &r = ci; // error: can't bind an ordinary int& to a const int object
const int &r2 = i; // ok: can bind const int& to plain int

Constant Expressions

A constant expression is an expression whose value cannot change and that can be evaluated at compile time.

A literal is a constant expression.

A const object that is initialized from a constant expression is also a constant expression.

const int max_files = 20; // max_files is a constant expression
const int limit = max_files + 1; // limit is a constant expression
int staff_size = 27; // staff_size is not a constant expression
const int sz = get_size(); // sz is not a constant expression

Constexpr Variables

In a large system, it can be difficult to determine (for certain) that an initializer is a constant expression.

In general, the definition of an object and its use in such a context can be widely separated.

Under the new standard, we can ask the compiler to verify that a variable is a constant expression by declaring the variable in a constexpr declaration.

Variables declared as constexpr are implicitly const and must be initialized by constant expressions:

constexpr int mf = 20; // 20 is a constant expression
constexpr int limit = mf + 1; // mf + 1 is a constant expression
constexpr int sz = size(); // ok only if size is a constexpr function

Although we cannot use an ordinary function as an initializer for a constexpr variable, the new standard lets us define certain
functions as constexpr.

Such functions must be simple enough that the compiler can evaluate them at compile time.

We can use constexpr functions in the initializer of a constexpr variable.

The types we can use in a constexpr are known as “literal types” because they are simple enough to have literal values.

Variables defined inside a function ordinarily are not stored at a fixed address. Hence, we cannot use a constexpr pointer to point to such variables.

On the other hand, the address of an object defined outside of any function is a constant expression, and so may be used to initialize a constexpr pointer.

Pointers and constexpr

It is important to understand that when we define a pointer in a constexpr declaration, the constexpr specifier applies to the pointer, not the type to which the pointer points:

const int *p = nullptr; // p is a pointer to a const int
constexpr int *q = nullptr; // q is a const pointer to int
发布了120 篇原创文章 · 获赞 2 · 访问量 5778

猜你喜欢

转载自blog.csdn.net/Lee567/article/details/104081829