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
|
Sometimes objects are used because they offer a certain functionality. The
objects only exist because of their functionality, and nothing in the objects
themselves is ever changed. The following class tt(Print) offers a facility to
print a string, using a configurable prefix and suffix. A partial class
interface could be:
verb( class Print
{
public:
Print(ostream &out);
void print(std::string const &prefix, std::string const &text,
std::string const &suffix) const;
};)
An interface like this would allow us to do things like:
verb( Print print{ cout };
for (int idx = 0; idx != argc; ++idx)
print.print("arg: ", argv[idx], "\n");)
This works fine, but it could greatly be improved if we could pass
tt(print)'s invariant arguments to tt(Print)'s constructor. This would
simplify tt(print)'s prototype (only one argument would need to be passed
rather than three) and we could wrap the above code in a function
expecting a tt(Print) object:
verb( void allArgs(Print const &print, int argc, char **argv)
{
for (int idx = 0; idx != argc; ++idx)
print.print(argv[idx]);
})
The above is a fairly generic piece of code, at least it is with respect
to tt(Print). Since tt(prefix) and tt(suffix) don't change they can be passed
to the constructor which could be given the prototype:
verb( Print(ostream &out, string const &prefix = "", string const &suffix = "");)
Now tt(allArgs) may be used as follows:
verb( Print p1{ cout, "arg: ", "\n" }; // prints to cout
Print p2{ cerr, "err: --", "--\n" };// prints to cerr
allArgs(p1, argc, argv); // prints to cout
allArgs(p2, argc, argv); // prints to cerr)
But now we note that tt(p1) and tt(p2) are only used inside the
tt(allArgs) function. Furthermore, as we can see from tt(print)'s prototype,
tt(print) doesn't modify the internal data of the tt(Print) object it is
using.
In such situations it is actually not necessary to define objects before
they are used. Instead emi(anonymous object)em(s)hi(object: anonymous) may be
used. Anonymous objects can be used:
itemization(
it() to initialize a function parameter which is a tt(const) reference to
an object;
it() if the object is em(only) used inside the function call.
)
When passing anonymous objects as arguments of tt(const &) parameters of
functions they are considered constant as they merely exist for passing the
information of (class type) objects to those functions. This way, they cannot
be modified, nor may their non-const member functions be used. Of course, a
tt(const_cast) could be used to cast away the const reference's constness, but
that's considered bad practice on behalf of the function receiving the
anonymous objects. Also, any modification to the anonymous object is lost once
the function returns as the anonymous object ceases to exist after calling the
function. These anonymous objects used to initialize const references should
not be confused with passing anonymous objects to parameters defined as rvalue
references (section ref(RREF)) which have a completely different purpose in
life. Rvalue references primarily exist to be `swallowed' by functions
receiving them. Thus, the information made available by rvalue references
outlives the rvalue reference objects which are also anonymous.
Anonymous objects are defined when a constructor is used without providing
a name for the constructed object. Here is the corresponding example:
verb( allArgs(Print{ cout, "arg: ", "\n" }, argc, argv); // prints to cout
allArgs(Print{ cerr, "err: --", "--\n" }, argc, argv);// prints to cerr)
In this situation the tt(Print) objects are constructed and immediately
passed as first arguments to the tt(allArgs) functions, where they are
accessible as the function's tt(print) parameter. While the tt(allArgs)
function is executing they can be used, but once the function has completed,
the anonymous tt(Print) objects are no longer accessible.
|