1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104
|
Anonymous objects can be used to initialize function parameters
that are tt(const) references to objects. These objects are created just
before such a function is called, and are destroyed once the function has
terminated. bf(C++)'s grammar allows us to use anonymous objects in other
situations as well. Consider the following snippet of code:
verb( int main()
{
// initial statements
Print{ "hello", "world" }; // assume a matching constructor
// is available
// later statements
})
In this example an anonymous tt(Print) object is constructed, and it is
immediately destroyed thereafter. So, following the `initial
statements' our tt(Print) object is constructed. Then it is destroyed again
followed by the execution of the `later statements'.
The example illustrates that the standard lifetime rules do not apply to
anonymous objects. hi(lifetime: anonymous objects)
hi(anonymous object: lifetime) Their lifetimes are limited to the
em(statements), rather than to the em(end of the block) in which they are
defined.
Plain anonymous object are at least useful in one situation. Assume we
want to put em(markers) in our code producing some output when the program's
execution reaches a certain point. An object's constructor could be
implemented so as to provide that marker-functionality allowing us to put
markers in our code by defining anonymous, rather than named objects.
bf(C++)'s grammar contains another remarkable characteristic illustrated
by the next example:
verb( int main(int argc, char **argv)
{
// assume a matching constructor is available:
Print p{ cout, "", "" }; // 1
allArgs(Print{ p }, argc, argv); // 2
})
In this example a non-anonymous object tt(p) is constructed in statement
1, which is then used in statement 2 to em(initialize) an anonymous
object. The anonymous object, in turn, is then used to initialize
tt(allArgs)'s tt(const) reference parameter. This use of an existing object
to initialize another object is common practice, and is based on the existence
of a so-called
emi(copy constructor). A copy constructor creates an object (as it is a
constructor) using an existing object's characteristics to initialize the data
of the object that's created. Copy constructors are discussed in depth in
chapter ref(MEMORY), but presently only the concept of a copy constructor is
used.
In the above example a copy constructor is used to initialize an anonymous
object. The anonymous object was then used to initialize a parameter of a
function. However, when we try to apply the same trick (i.e., using an
existing object to initialize an anonymous object) to a plain statement, the
compiler generates an error: the object tt(p) can't be redefined (in statement
3, below):
verb( int main(int argc, char *argv[])
{
Print p{ "", "" }; // 1
allArgs(Print(p), argc, argv); // 2
Print(p); // 3 error!
})
Does this mean that using an existing object to initialize an anonymous
object that is used as function argument is OK, while an existing object can't
be used to initialize an anonymous object in a plain statement?
The compiler actually provides us with the answer to this apparent
contradiction. About statement 3 the compiler reports something like:
verb( error: redeclaration of 'Print p')
which solves the problem when realizing that within a compound statement
objects and variables may be defined. Inside a compound statement, a
em(type name) followed by a tt(variable name) is the grammatical form of a
variable definition. em(Parentheses) can be used to break priorities, but if
there are no priorities to break, they have no effect, and are simply ignored
by the compiler. In statement 3 the parentheses allowed us to get rid of the
blank that's required between a type name and the variable name, but to the
compiler we wrote
verb( Print (p);)
which is, since the parentheses are superfluous, equal to
verb( Print p;)
thus producing tt(p)'s redeclaration.
As a further example: when we define a variable using a built-in type (e.g.,
tt(double)) using superfluous parentheses the compiler quietly removes
these parentheses for us:
verb( double ((((a)))); // weird, but OK.)
To summarize our findings about anonymous variables:
itemization(
it() Anonymous objects are great for initializing tt(const) reference
parameters.
it() The same syntax, however, can also be used in stand-alone
statements, in which they are interpreted as variable definitions if our
intention actually was to initialize an anonymous object using an existing
object.
it() Since this may cause confusion, it's probably best to restrict the
use of anonymous objects to the first (and main) form: initializing function
parameters.
)
|