An Overture to C Programmers
liii
This is the resource allocation is initialization (RAII) concept (sometimes
also called
constructor acquires, destructor releases). Consider the C++ class in
Listing 24.
#include
#include
struct File {
File(const char* path, bool write) {
u
auto file_mode = write ? "w" : "r";
v
file_pointer = fopen(path, file_mode);
w
if (!file_pointer) throw std::system_error(errno, std::system_category());
x
}
~File() {
fclose(file_pointer);
}
FILE* file_pointer;
};
Listing 24: A
File
class
The constructor of
File
u
takes two arguments. The first argument cor-
responds with the
path
of the file, and the second is a
bool
corresponding to
whether the file mode should be open for write (
true
) or read (
false
). This
argument’s value sets
file_mode
v
via the ternary operator
? :
. The ternary
operator evaluates a Boolean expression and returns one of two values
depending on the Boolean value. For example:
x ? val_if_true : val_if_false
If the Boolean expression
x
is
true
, the expression’s value is
val_if_true
.
If
x
is
false
, the value is
val_if_false
instead.
In the
File
constructor code snippet in Listing 24, the constructor
attempts to open the file at
path
with read/write access
w
. If anything goes
wrong, the call will set
file_pointer
to
nullptr
, a special C++ value that’s
similar to 0. When this happens, you throw a
system_error
x
. A
system_error
is just an object that encapsulates the details of a system error. If
file_pointer
isn’t
nullptr
, it’s valid to use. That’s this class’s invariant.
Now consider the program in Listing 25, which employs
File
.
#include
#include
#include
struct File {
--
snip
—
};
int main() {
{
u
File file("last_message.txt", true);
v
const auto message = "We apologize for the inconvenience.";
liv
An Overture to C Programmers
fwrite(message, strlen(message), 1, file.file_pointer);
}
w
// last_message.txt is closed here!
{
File file("last_message.txt", false);
x
char read_message[37]{};
fread(read_message, sizeof(read_message), 1, file.file_pointer);
printf("Read last message: %s\n", read_message);
}
}
We apologize for the inconvenience.
Listing 25: A program employing the
File
class
The braces
u
w
define a scope. Because the first
file
resides within this
scope, the scope defines the lifetime of
file
. Once
the constructor returns
v
,
you know that
file.file_pointer
is valid thanks to the class invariant; based on
the design of the constructor of
File
, you know
file.file_pointer
must be valid
for the lifetime of the
File
object. You write a message using
fwrite
. There’s
no need to call
fclose
explicitly, because
file
expires
and the destructor
cleans up
file.file_pointer
for you
v
. You open
File
again but this time for
read access
x
. As long as the constructor returns, you know that
last_message
.txt was opened successfully and continue on reading into
read_message
. After
printing the message, the destructor of
file
is called, and the
file.file_pointer
is again cleaned up.
Sometimes you need the flexibility
of dynamic memory allocation, but
you still want to lean on the object life cycle of C++ to ensure that you don’t
leak memory or accidentally “use after free.” This is exactly the role of
smart
pointers, which manage the life cycle of dynamic objects through an ownership
model. Once no smart pointer owns a dynamic object, the object destructs.
One such smart pointer is
unique_ptr
, which models exclusive owner-
ship. Listing 26 illustrates its basic usage.
#include
struct Foundation{
const char* founder;
};
int main() {
std::unique_ptr second_foundation{ new Foundation{} };
u
// Access founder member variable just like a pointer:
second_foundation->founder = "Wanda";
}
v
Listing 26: A program employing a
unique_ptr
You dynamically allocate a
Foundation
, and the resulting
Foundation*
pointer is passed into the constructor of
second_foundation
using the
An Overture to C Programmers
lv
braced-initialization
syntax
u
. The
second_foundation
has type
unique_ptr
,
which is just an RAII object wrapping the dynamic
Foundation
. When
second
_foundation
is destructed
v
, the dynamic
Foundation
destructs appropriately.
Smart pointers differ from regular,
raw pointers because a raw pointer
is simply a memory address. You must orchestrate all the memory manage-
ment that’s involved with the address manually. On the other hand, smart
pointers handle all these messy details. By wrapping a dynamic object with
a smart pointer, you can rest assured that memory
will be cleaned up appro-
priately as soon as the object is no longer needed. The compiler knows that
the object is no longer needed because the smart pointer’s destructor is
called when it falls out of scope.
Dostları ilə paylaş: