The c++ programming language 4th Edition by bjarne stroustrup.pdf ...

1 downloads 75 Views 19MB Size Report
Upper Saddle River, NJ • Boston • Indianapolis • San Francisco. New York • Totonto ... with initial capital lett
The C++ Programming Language Fourth Edition

Bjarne Stroustrup

Upper Saddle River, NJ • Boston • Indianapolis • San Francisco New York • Totonto • Montreal • London • Munich • Paris • Madrid Capetown • Sydney • Tokyo • Singapore • Mexico City

Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in this book, and the publisher was aware of a trademark claim, the designations have been printed with initial capital letters or in all capitals. The author and publisher have taken care in the preparation of this book, but make no expressed or implied warranty of any kind and assume no responsibility for errors or omissions. No liability is assumed for incidental or consequential damages in connection with or arising out of the use of the information or programs contained herein. The publisher offers excellent discounts on this book when ordered in quantity for bulk purchases or special sales, which may include electronic versions and/or custom covers and content particular to your business, training goals, marketing focus, and branding interests. For more information, please contact: U.S. Corporate and Government Sales (800) 382-3419 [email protected] For sales outside the United States, please contact: International Sales [email protected] Visit us on the Web: informit.com/aw Library of Congress Cataloging-in-Publication ) cerr int conversion // works if and only if the type of f() can be implicitly converted to a T

When we use auto, there is only one type involved, the type of the initializer, and we can safely use the = syntax: auto v1 = 12345; auto v2 = 'c'; auto v3 = f();

// v1 is an int // v2 is a char // v3 is of some appropriate type

In fact, it can be an advantage to use the prise someone: auto v1 {12345}; auto v2 {'c'}; auto v3 {f()};

=

syntax with

// v1 is a list of int // v2 is a list of char // v3 is a list of some appropriate type

This is logical. Consider:

auto,

because the {}-list syntax might sur-

Section 6.3.6.2

auto x0 {}; auto x1 {1}; auto x2 {1,2}; auto x3 {1,2,3};

auto

and {}-lists

165

// error: cannot deduce a type // list of int with one element // list of int with two elements // list of int with three elements

The type of a homogeneous list of elements of type T is taken to be of type initializer_list (§3.2.1.3, §11.3.3). In particular, the type of x1 is not deduced to be int. Had it been, what would be the types of x2 and x3? Consequently, I recommend using = rather than {} for objects specified auto whenever we don’t mean ‘‘list.’’

6.3.6.3 The decltype() Specifier We can use auto when we have a suitable initializer. But sometimes, we want to have a type deduced without defining an initialized variable. Then, we can use a declaration type specifier: decltype(expr) is the declared type of expr. This is mostly useful in generic programming. Consider writing a function that adds two matrices with potentially different element types. What should be the type of the result of the addition? A matrix, of course, but what might its element type be? The obvious answer is that the element type of the sum is the type of the sum of the elements. So, I can declare: template auto operator+(const Matrix& a, const Matrix& b) −> Matrix;

I use the suffix return type syntax (§12.1) to be able to express the return type in terms of the arguments: Matrix. That is, the result is a Matrix with the element type being what you get from adding a pair of elements from the argument Matrixes: T{}+U{}. In the definition, I again need decltype() to express Matrix’s element type: template auto operator+(const Matrix& a, const Matrix& b) −> Matrix { Matrix res; for (int i=0; i!=a.rows(); ++i) for (int j=0; j!=a.cols(); ++j) res(i,j) += a(i,j) + b(i,j); return res; }

6.4 Objects and Values We can allocate and use objects that do not have names (e.g., created using new), and it is possible to assign to strange-looking expressions (e.g., ∗p[a+10]=7). Consequently, we need a name for ‘‘something in memory.’’ This is the simplest and most fundamental notion of an object. That is, an object is a contiguous region of storage; an lvalue is an expression that refers to an object. The word ‘‘lvalue’’ was originally coined to mean ‘‘something that can be on the left-hand side of an assignment.’’ However, not every lvalue may be used on the left-hand side of an assignment; an

166

Types and Declarations

Chapter 6

lvalue can refer to a constant (§7.7). An lvalue that has not been declared const is often called a modifiable lvalue. This simple and low-level notion of an object should not be confused with the notions of class object and object of polymorphic type (§3.2.2, §20.3.2).

6.4.1 Lvalues and Rvalues To complement the notion of an lvalue, we have the notion of an rvalue. Roughly, rvalue means ‘‘a value that is not an lvalue,’’ such as a temporary value (e.g., the value returned by a function). If you need to be more technical (say, because you want to read the ISO C++ standard), you need a more refined view of lvalue and rvalue. There are two properties that matter for an object when it comes to addressing, copying, and moving: • Has identity: The program has the name of, pointer to, or reference to the object so that it is possible to determine if two objects are the same, whether the value of the object has changed, etc. • Movable: The object may be moved from (i.e., we are allowed to move its value to another location and leave the object in a valid but unspecified state, rather than copying; §17.5). It turns out that three of the four possible combinations of those two properties are needed to precisely describe the C++ language rules (we have no need for objects that do not have identity and cannot be moved). Using ‘‘m for movable’’ and ‘‘i for has identity,’’ we can represent this classification of expressions graphically: lvalue {i&!m}

xvalue {i&m} glvalue {i}

prvalue {!i&m} rvalue {m}

So, a classical lvalue is something that has identity and cannot be moved (because we could examine it after a move), and a classical rvalue is anything that we are allowed to move from. The other alternatives are prvalue (‘‘pure rvalue’’), glvalue (‘‘generalized lvalue’’), and xvalue (‘‘x’’ for ‘‘extraordinary’’ or ‘‘expert only’’; the suggestions for the meaning of this ‘‘x’’ have been quite imaginative). For example: void f(vector& vs) { vector& v2 = std::move(vs); // ... }

// move vs to v2

Here, std::move(vs) is an xvalue: it clearly has identity (we can refer to it as vs), but we have explicitly given permission for it to be moved from by calling std::move() (§3.3.2, §35.5.1). For practical programming, thinking in terms of rvalue and lvalue is usually sufficient. Note that every expression is either an lvalue or an rvalue, but not both.

6.4.2 Lifetimes of Objects The lifetime of an object starts when its constructor completes and ends when its destructor starts executing. Objects of types without a declared constructor, such as an int, can be considered to have default constructors and destructors that do nothing.

Section 6.4.2

Lifetimes of Objects

167

We can classify objects based on their lifetimes: • Automatic: Unless the programmer specifies otherwise (§12.1.8, §16.2.12), an object declared in a function is created when its definition is encountered and destroyed when its name goes out of scope. Such objects are sometimes called automatic objects. In a typical implementation, automatic objects are allocated on the stack; each call of the function gets its own stack frame to hold its automatic objects. • Static: Objects declared in global or namespace scope (§6.3.4) and statics declared in functions (§12.1.8) or classes (§16.2.12) are created and initialized once (only) and ‘‘live’’ until the program terminates (§15.4.3). Such objects are called static objects. A static object has the same address throughout the life of a program execution. Static objects can cause serious problems in a multi-threaded program because they are shared among all threads and typically require locking to avoid [3]=='a'. Such cleverness has no place in production code. These equivalences are pretty low-level and do not hold for standard-library containers, such as array and vector. The result of applying the arithmetic operators +, −, ++, or −− to pointers depends on the type of the object pointed to. When an arithmetic operator is applied to a pointer p of type T∗, p is assumed to point to an element of an array of objects of type T; p+1 points to the next element of that array, and p−1 points to the previous element. This implies that the integer value of p+1 will be sizeof(T) larger than the integer value of p. For example: template int byte_diff(T∗ p, T∗ q) { return reinterpret_cast(q)−reinterpret_cast(p); } void diff_test() { int vi[10]; short vs[10];

182

Pointers, Arrays, and References

Chapter 7

cout

Suggest Documents