天天看點

《C++ Primer第五版》讀書筆記(17)--Specialized Tools and Techniques

19.1 Controlling Memory Allocation

Some applications have specialized memory allocation needs that cannot be met by the standard memory management facilities. They can overload the new and delete operators to control memory allocation.

19.1.1 Overloading new and delete

When we define the global operator new and operator delete functions, we take over responsibility for all dynamic memory allocation. These functions must be correct: They form a vital part of all processing in the program.

The operator new and operator delete Interface

The library defines eight overloaded versions of operator new and delete functions. The first four support the versions of new that can throw a bad_alloc exception. The next four support nonthrowing versions of new:

// these versions might throw an exception

void *operator new(size_t);  // allocate an object

void *operator new[](size_t);  // allocate an array

void *operator delete(void*) noexcept;  // free an object

void *operator delete[](void*) noexcept; // free an array

// versions that promise not to throw; see § 12.1.2 (p. 460)

void *operator new(size_t, nothrow_t&) noexcept;

void *operator new[](size_t, nothrow_t&) noexcept;

void *operator delete(void*, nothrow_t&) noexcept;

void *operator delete[](void*, nothrow_t&) noexcept;

The malloc and free Functions

If you define your own global operator new and operator delete, those functions must allocate and deallocate memory somehow. Even if you define these functions in order to use a specialized memory allocator, it can still be useful for testing purposes to be able to allocate memory similarly to how the implementation

To this end, we can use functions named malloc and free that C++ inherits from C. These functions, are defined in cstdlib.

19.1.2 Placement new Expressions

In earlier versions of the language—before the allocator(§ 12.2.2, p. 481) class was part of the library—applications that wanted to separate allocation from initialization did so by calling operator newand operator delete. These functions behave analogously to the allocate and deallocate members of allocator. Like those members, operator new and operator delete functions allocate and deallocate memory but do not construct or destroy objects.

We use the placement new form of new to construct an object.

new (place_address) type

new    (place_address) type(initializers)

new    (place_address) type[size]

new    (place_address) type[size] { braced initializer list}

When passed a single argument that is a pointer, a placement new expression constructs an object but does not allocate memory.

19.2 Run-Time Type Identification

Run-time type identification(RTTI) is provided through two operators:

•The typeid operator, which returns the type of a given expression

•The dynamic_cast operator, which safely converts a pointer or reference to a base type into a pointer or reference to a derived type

Ordinarily, we should use virtual functions if we can. When the operation is virtual, the compiler automatically selects the right function according to the dynamic type of the object. However,it is not always possible to define a virtual. If we cannot use a virtual, we can use one of the RTTI operators. On the other hand, using these operators is more error-prone than using virtual member functions: The programmer must know to which type the object should be cast and must check that the cast was performed successfully.

19.2.1 The dynamic_cast Operator

A dynamic_cast has the following form:

dynamic_cast<type*>(e)

dynamic_cast<type&>(e)

dynamic_cast<type&&>(e)

where type must be a class type and (ordinarily) names a class that has virtual functions. In the first case, emust be a valid pointer (§ 2.3.2, p. 52); in the second, e must be an lvalue; and in the third, emust not be an lvalue.

In all cases, the type of e must be either a class type that is publicly derived from the target type, a public base class of the target type, or the same as the target type.

Pointer-Type dynamic_casts

if(Derived *dp = dynamic_cast<Derived*>(bp))

{

// use the Derived object to which dp points

} else {  // bp points at a Base object

// use the Base object to which bp points

}

Reference-Type dynamic_casts

void f(const Base &b)

{

try {

const Derived &d = dynamic_cast<const Derived&>(b);

// use the Derived object to which b referred

} catch (bad_cast) {

// handle the fact that the cast failed

}

}

19.2.2 The typeid Operator

Derived *dp = new Derived;

Base *bp = dp;  // both pointers point to a Derived object

// compare the type of two objects at run time

if (typeid(*bp) == typeid(*dp)) {

// bp and dp point to objects of the same type

}

// test whether the run-time type is a specific type

if (typeid(*bp) == typeid(Derived)) {

// bp actually points to a Derived

}

Note that the operands to the typeidare objects—we used *bp, not bp. The typeidof a pointer (as opposed to the object to which the pointer points) returns the static, compile-time type of the pointer:

// test always fails: the type of bp is pointer to Base

if (typeid(bp) == typeid(Derived)) {

// code never executed

}

19.2.3 The type_info Class

The exact definition of the type_info class varies by compiler. However, the standard guarantees that the class will be defined in the typeinfoheader and that the class will provide at least the operations listed in Table 19.1.

19.3 Enumerations

Enumerations let us group together sets of integral constants. Like classes, each enumeration defines a new type. Enumerations are literal types.

C++ has two kinds of enumerations: scoped and unscoped. The new standard introduced scoped enumerations. We define a scoped enumeration using the keywords enum class(or, equivalently, enum struct), followed by the enumeration name and a comma-separated list of enumerators enclosed in curly braces. A semicolon follows the close curly:

enum class open_modes {input, output, append};

Here we defined an enumeration type named open_modes that has three enumerators: input, output, and append.

We define an unscoped enumeration by omitting the class(or struct) keyword. The enumeration name is optional in an unscoped enum:

enum color {red, yellow, green};  // unscoped enumeration

// unnamed, unscoped enum

enum {floatPrec = 6, doublePrec = 10, double_doublePrec =10};

enum color {red, yellow, green};  // unscoped enumeration

enum stoplight {red, yellow, green};  // error: redefines enumerators

enum class peppers {red, yellow, green}; // ok: enumerators are hidden

color eyes = green; // ok: enumerators are in scope for an unscoped enumeration

peppers p = green;  // error: enumerators from peppers are not in scope

//  color::green is in scope but has the wrong type

color hair = color::red;  // ok: we can explicitly access the enumerators

peppers p2 = peppers::red; // ok: using red from peppers

By default, enumerator values start at 0 and each enumerator has a value 1 greater than the preceding one. However, we can also supply initializers for one or more enumerators:

enum class intTypes {

charTyp = 8, shortTyp = 16, intTyp = 16,longTyp = 32, long_longTyp = 64

};

When we omit an initializer, the enumerator has a value 1 greater than the preceding enumerator.

Like Classes, Enumerations Define New Types

So long as the enumis named, we can define and initialize objects of that type.

An enum object may be initialized or assigned only by one of its enumerators or by another object of the same enum type:

open_modes om = 2;  // error: 2 is not of type open_modes

om = open_modes::input; // ok: input is an enumerator of open_modes

Objects or enumerators of an unscoped enumeration type are automatically converted to an integral type. As a result, they can be used where an integral value is required:

int i = color::red;  // ok: unscoped enumerator implicitly converted to int

int j = peppers::red; // error: scoped enumerations are not implicitly converted

Specifying the Size of an enum

Although each enumdefines a unique type, it is represented by one of the built-in integral types.Under the new standard, we may specify that type by following the enum name with a colon and the name of the type we want to use:

enum intValues : unsigned long long {

charTyp = 255, shortTyp = 65535, intTyp = 65535,

longTyp = 4294967295UL,

long_longTyp = 18446744073709551615ULL

};

If we do not specify the underlying type, then by default scoped enums have int as the underlying type. There is no default for unscoped enums; all we know is that the underlying type is large enough to hold the enumerator values.

Being able to specify the underlying type of an enumlets us control the type used across different implementations. We can be confident that our program compiled under one implementation will generate the same code when we compile it on another.

Forward Declarations for Enumerations

Parameter Matching and Enumerations

Because an object of enumtype may be initialized only by another object of that enum type or by one of its enumerators (§ 19.3, p. 833), an integral value that happens to have the same value as an enumerator cannot be used to call a function expecting an enum argument.

From “Thinking in C++”:

 Enumeration Types的引入目的:提供一種方法,它不僅能夠定義而且能夠将一系列整形常量放在一個組内,這可以避免無法控制值的範圍的問題。例如在函數入參或者傳回值時,我們說它隻有那麼幾個入參或者幾個傳回值,為了保證其不會超過這個範圍,那麼最好的辦法就是将入參或者傳回值設定為enumeration對象。

 特性(與其引入目的緊密相關):一個Enumeration對象隻能用另一個相同的enumeration對象或者其enumerator set中的一個來初始化或者指派,而不能使用int型的literal constant 直接為其指派。但是如果需要,它可以轉換為int類型。

19.4 Pointer to Class Member

A pointer to member is a pointer that can point to a nonstatic member of a class. Normally a pointer points to an object, but a pointer to member identifies a member of a class, not an object of that class.

static class members are not part of any object, so no special syntax is needed to point to a staticmember. Pointers to staticmembers are ordinary pointers.

19.4.1 Pointers to Data Members

class Screen {

public:

typedef  std::string::size_type pos;

char get_cursor() const { return contents[cursor]; }

char get() const;

char get(pos ht, pos wd) const;

private:

std::string contents;

pos cursor;

pos height, width;

};

// pdata can point to a string member of a const (or non const) Screen object

const string Screen::*pdata;

pdata= &Screen::contents;

Of course, under the new standard, the easiest way to declare a pointer to member is to use auto or decltype:

auto pdata = &Screen::contents;

Using a Pointer to Data Member

Screen myScreen, *pScreen = &myScreen;

// .* dereferences pdata to fetch the contents member from the object myScreen

auto s = myScreen.*pdata;

// ->* dereferences pdata to fetch contents from the object to which pScreen points

s = pScreen->*pdata;

19.4.2 Pointers to Member Functions

char (Screen::*pmf2)(Screen::pos, Screen::pos) const;

pmf2 = &Screen::get;

// pmf points to a Screen member that takes no arguments and returns char

pmf = &Screen::get; // must explicitly use the address-of operator

pmf = Screen::get;  // error: no conversion to pointer for member functions

Using a Pointer to Member Function

Screen myScreen,*pScreen = &myScreen;

// call the function to which pmf points on the object to which pScreen points

char c1 = (pScreen->*pmf)();

// passes the arguments 0, 0 to the two-parameter version of get on the object myScreen

char c2 = (myScreen.*pmf2)(0, 0);

Using Type Aliases for Member Pointers

Type aliases make code that uses pointers to members much easier to read and write.

using Action = char (Screen::*)(Screen::pos, Screen::pos) const;

Action get = &Screen::get; // get points to the get member of Screen

// action takes a reference to a Screen and a pointer to a Screen member function

Screen& action(Screen&, Action = &Screen::get);

Screen myScreen;

// equivalent calls:

action(myScreen);  // uses the default argument

action(myScreen, get); // uses the variable get that we previously defined

action(myScreen, &Screen::get); // passes the address explicitly

Pointer-to-Member Function Tables

One common use for function pointers and for pointers to member functions is to store them in a function table (§ 14.8.3, p. 577). For a class that has several members of the same type, such a table can be used to select one from the set of these members.

19.4.3 Using Member Functions as Callable Objects

Unlike ordinary function pointers, a pointer to member is nota callable object; these pointers do not support the function-call operator

Using function to Generate a Callable

One way to obtain a callable from a pointer to member function is by using the library function template:

function<bool(const string&)>  fcn = &string::empty;

find_if(svec.begin(), svec.end(), fcn);

Here we tell function that empty is a function that can be called with a string and returns a bool.

Using mem_fn to Generate a Callable

To use function, we must supply the call signature of the member we want to call. We can, instead, let the compiler deduce the member’s type by using another library facility, mem_fn, which, like function, is defined in the functional header.

find_if(svec.begin(),svec.end(), mem_fn(&string::empty));

Using bind to Generate a Callable

// bind each string in the range to the implicit first argument to empty

auto it = find_if(svec.begin(), svec.end(), bind(&string::empty, _1));

19.5 Nested Classes

A class can be defined within another class. Such a class is a nested class, also referred to as a nested type. Nested classes are most often used to define implementation classes.

The name of a nested class is visible within its enclosing class scope but not outside the class.

Declaring a Nested Class

class TextQuery {

public:

class QueryResult; // nested class to be defined later

// other members as in § 12.3.2 (p. 487)

};

Defining a Nested Class outside of the Enclosing Class

// we're defining the QueryResult class that is a member of class TextQuery

class TextQuery::QueryResult {

};

Defining the Members of a Nested Class

TextQuery::QueryResult::QueryResult(string s,shared_ptr<set<line_no>> p,shared_ptr<vector<string>> f):sought(s), lines(p), file(f) { }

The Nested and Enclosing Classes Are Independent

19.6 union: A Space-Saving Class

Defining a union

// objects of type Token have a single member, which could be of any of the listed types

union Token {

// members are public by default

char  cval;

int  ival;

double dval;

};

Using a unionType

Token first_token = {'a'}; // initializes the cval member

Token last_token;   // uninitialized Token object

Token  *pt = new Token;  // pointer to an uninitialized Token object

19.7 Local Classes

A class can be defined inside a function body. Such a class is called a local class.  All members, including functions, of a local class must be completely defined inside the class body. As a result, local classes are much less useful than nested classes.

In practice, the requirement that members be fully defined within the class limits the complexity of the member functions of a local class. Functions in local classes are rarely more than a few lines of code. Beyond that, the code becomes difficult for the reader to understand.

19.8 Inherently Nonportable Features

To support low-level programming, C++ defines some features that are inherently nonportable. A nonportable feature is one that is machine specific. Programs that use nonportable features often require reprogramming when they are moved from one machine to another.

19.8.1 Bit-fields

A class can define a (nonstatic) data member as a bit-field. A bit-field holds a specified number of bits. Bit-fields are normally used when a program needs to pass binary data to another program or to a hardware device.

Ordinarily it is best to make a bit-field an unsignedtype. The behavior of bit-fields stored in a signed type is implementation defined.

19.8.2 volatile Qualifier

The precise meaning of volatile is inherently machine dependent and can be understood only by reading the compiler documentation. Programs that use volatile usually must be changed when they are moved to new machines or compilers.

    volatile對于單片機開發等技術相當重要。編譯器在優化代碼時,看到該寄存器一直沒有再次被寫入過,就會認為該變量是最新的,直接從該寄存器區數讀據;但是增加了volatile後,它每次都會重新讀取資料。它的用法與const完全相同,也可以用于變量、指針、函數參數與傳回值、類等。而且volatile與const是可以一起使用的,這種用法并沒有任何沖突之處。

19.8.3 Linkage Directives: extern "C"

C++ programs sometimes need to call functions written in another programming language. Most often, that other language is C. Like any name, the name of a function written in another language must be declared. As with any function, that declaration must specify the return type and parameter list. The compiler checks calls to functions written in another language in the same way that it handles ordinary C++ functions. 

However, the compiler typically must generate different code to call functions written in other languages. C++ uses linkage directives to indicate the language used for any non-C++ function. 

Declaring a Non-C++ Function

// illustrative linkage directives that might appear in the C++ header <cstring>

// single-statement linkage directive

extern "C" size_t strlen(const char *);

// compound-statement linkage directive

extern "C" {

int strcmp(const char*, const char*);

char *strcat(char*, const char*);

}

Linkage Directives and Headers

When a #include directive is enclosed in the braces of a compound-linkage directive, all ordinary function declarations in the header file are assumed to be functions written in the language of the linkage directive.

// compound-statement linkage directive

extern "C" {

#include <string.h>  // C functions that manipulate C-style strings

}

Preprocessor Support for Linking to C

To allow the same source file to be compiled under either C or C++, the preprocessor defines _ _cplusplus(two underscores) when we compile C++. Using this variable, we can conditionally include code when we are compiling C++:

#ifdef__cplusplus

// ok: we're compiling C++

extern "C"

#endif

int strcmp(const char*, const char*);

Overloaded Functions and Linkage Directives

// error: two extern "C" functions with the same name

extern "C" void print(const char*);

extern "C" void print(int);

 連結:要使用一個庫,首先需要包括該庫的頭檔案,然後可以使用它的函數和變量。最後在link的時候,linker先檢索源代碼中是否有該聲明的定義,如果有該聲明的定義則直接連接配接進去,否則根據順序到指定的庫中去查找該函數的定義,并将該函數定義部分的代碼放入要生成的可執行程式中,注意僅僅是該部分定義,而不是整個庫。庫會存有對該函數的索引,是以不需要在整個庫中搜尋,而是先在該索引中定位。這就是說,你可以定一個與庫中函數一樣的函數,編譯到工程中的時候,它有較高的優先級,是以它能替代庫中的函數代碼段。