An Overture to C Programmers
xlvii
// C99
for (size_t i=0; i
v
// C++17
for (auto& x : v) x = 0;
w
}
Listing 16: A program illustrating several ways to iterate over an array
This code snippet shows the different ways
to declare loops in ANSI-C,
C99, and C++. The index variable
i
in the ANSI-C
u
and C99
v
examples are
ancillary to what you’re trying to accomplish, which is to access each element
of
v
. The C++ version
w
utilizes a
range-based
for
loop,
which loops over in the
range of values in
v
while hiding the details of how iteration is achieved. Like
a lot of the zero-overhead abstractions in C++, this construct enables you to
focus on meaning rather than syntax. Range-based
for
loops work with many
types, and you can even make them work with user-defined types.
Speaking of user-defined types, they allow you to express ideas directly in
code. Suppose
you want to design a function,
navigate_to
, that tells a hypotheti-
cal robot to navigate to some position given x and y coordinates. Consider the
following prototype function:
void navigate_to(double x, double y);
What are
x
and
y
? What are their units? Your user must read the docu-
mentation (or possibly the source) to find out. Compare the following
improved prototype:
struct Position{
--
snip
--
};
void navigate_to(const Position& p);
This function is far clearer. There is no ambiguity about what
navigate_to
accepts. As long as you
have a validly constructed
Position
, you know exactly
how to call
navigate_to
. Worrying about units, conversions, and so on is now
the responsibility
of whoever constructs the
Position
class.
You can also come close to this clarity in C99/C11 using a
const
pointer,
but C++ also makes return types compact and expressive. Suppose you
want to write a corollary function for the robot called
get_position
that—
you guessed it—gets the position. In C, you have two options, as shown in
Listing 17.
Position* get_position();
u
void get_position(Position* p);
v
Listing 17: A C-style API for returning a user-defined type
In the first option, the caller is responsible for cleaning up the return
value
u
, which has probably incurred a dynamic allocation (although this
is unclear from the code). The caller is responsible for allocating a
Position
xlviii
An Overture to C Programmers
somewhere and passing it into
get_position
v
. This
latter approach is more
idiomatic C-style, but the language is getting in the way: you’re just trying
to get a position object, but you have to worry about whether the caller or
the called function is responsible for allocating and deallocating memory.
C++ lets you do all of this succinctly by returning user-defined types directly
from functions, as shown in Listing 18.
Position
u
get_position() {
--snip--
}
void navigate() {
auto p = get_position();
v
// p is now available for use
--snip--
}
Listing 18: Returning a user-defined type by value in C++
Because
get_position
returns a value
u
,
the compiler can elide the copy,
so it’s as if you’ve constructed an automatic
Position
variable directly
v
;
there’s no runtime overhead. Functionally, you’re in very similar territory
to the C-style pass by reference of Listing 17.
Dostları ilə paylaş: