Reader small image

You're reading from  Modern C++ Programming Cookbook - Third Edition

Product typeBook
Published inFeb 2024
PublisherPackt
ISBN-139781835080542
Edition3rd Edition
Right arrow
Author (1)
Marius Bancila
Marius Bancila
author image
Marius Bancila

Marius Bancila is a software engineer with two decades of experience in developing solutions for line of business applications and more. He is the author of The Modern C++ Challenge and Template Metaprogramming with C++. He works as a software architect and is focused on Microsoft technologies, mainly developing desktop applications with C++ and C#. He is passionate about sharing his technical expertise with others and, for that reason, he has been recognized as a Microsoft MVP for C++ and later developer technologies since 2006. Marius lives in Romania and is active in various online communities.
Read more about Marius Bancila

Right arrow

Working with Files and Streams

One of the most important parts of the C++ standard library is the input/output (I/O) stream-based library that enables developers to work with files, memory streams, or other types of I/O devices. The first part of this chapter provides solutions to some common stream operations, such as reading and writing data, localization settings, and manipulating the input and output of a stream. The second part of the chapter explores the C++17 filesystem library, which enables developers to perform operations with the filesystem and its objects, such as files and directories.

The recipes covered in this chapter are as follows:

  • Reading and writing raw data from/to binary files
  • Reading and writing objects from/to binary files
  • Using streams on fixed-size external buffers
  • Using localized settings for streams
  • Using I/O manipulators to control the output of a stream
  • Using monetary I/O manipulators
  • Using time I/O manipulators...

Reading and writing raw data from/to binary files

Some of the data programs you work with must be persisted to disk files in various ways, including storing data in a database or flat files, either as text or binary data. This recipe, and the next one, are focused on persisting and loading both raw data and objects from and to binary files.

In this context, raw data means unstructured data, and, in this recipe, we will consider writing and reading the content of a buffer (that is, a contiguous sequence of memory), which can either be an array, a std::vector, or a std::array.

Getting ready

For this recipe, you should be familiar with the standard stream I/O library, although some explanations, to the extent that is required to understand this recipe, are provided next. You should also be familiar with the differences between binary and text files.

In this recipe, we will use the ofstream and ifstream classes, which are available in the std namespace in the <fstream...

Reading and writing objects from/to binary files

In the previous recipe, we learned how to write and read raw data (that is, unstructured data) to and from a file. Many times, however, we must persist and load objects instead. Writing and reading in the manner shown in the previous recipe works for POD types only. For anything else, we must explicitly decide what is actually written or read, since writing or reading pointers (including those to virtual tables) and any sort of metadata is not only irrelevant but also semantically wrong. These operations are commonly referred to as serialization and deserialization. In this recipe, we will learn how to serialize and deserialize both POD and non-POD types to and from binary files.

Getting ready

For the examples in this recipe, we will use the foo and foopod classes, as follows:

class foo
{
  int         i;
  char        c;
  std::string s;
public:
  foo(int const i = 0, char const c = 0, std::string const & s = {}):
...

Using streams on fixed-size external buffers

The <strstream> header has been part of the standard I/O library from its beginning. It contains classes that provide stream operations on sequences of characters stored in an array. However, this header was deprecated a long time ago, in C++98, although it’s still available because a replacement wasn’t available. The C++20 standard has introduced the std::span class, which is a non-owning view of a sequence of objects. In C++23, a new header, <spanstream>, has been added as a replacement for <strstream>. This contains classes that provide stream operations on externally provided memory buffers. In this recipe, we’ll learn how to parse or write text using the I/O span streams.

How to do it…

Use the new C++23 span streams as follows:

  • To parse text from an external array, use std::ispanstream:
    char text[] = "1 1 2 3 5 8";
    std::ispanstream is{ std::span<char...

Using localized settings for streams

How writing or reading to and from streams is performed may depend on the language and regional settings. Examples include writing and parsing numbers, time values, or monetary values, or comparing (collating) strings. The C++ I/O library provides a general-purpose mechanism for handling internationalization features through locales and facets. In this recipe, you will learn how to use locales to control the behavior of input/output streams.

Getting ready

All of the examples in this recipe use the std::cout predefined console stream object. However, the same applies to all I/O stream objects. Also, in these recipe examples, we will use the following objects and lambda function:

auto now = std::chrono::system_clock::now();
auto stime = std::chrono::system_clock::to_time_t(now);
auto ltime = std::localtime(&stime);
std::vector<std::string> names
  {"John", "adele", "Øivind", "Franç...

Using I/O manipulators to control the output of a stream

Apart from the stream-based I/O library, the standard library provides a series of helper functions, called manipulators, that control the input and output streams using operator<< and operator>>. In this recipe, we will look at some of these manipulators and demonstrate their use through some examples that format the output to the console. We will continue covering more manipulators in the upcoming recipes.

Getting ready

The I/O manipulators are available in the std namespace in the headers <ios>, <istream>, <ostream>, and <iomanip>. In this recipe, we will only discuss some of the manipulators from <ios> and <iomanip>.

How to do it...

The following manipulators can be used to control the output or input of a stream:

  • boolalpha and noboolalpha enable and disable the textual representation of Booleans:
    std::cout << std::boolalpha <<...

Using monetary I/O manipulators

In the previous recipe, we looked at some of the manipulators that can be used to control input and output streams. The manipulators that we discussed were related to numeric values and text values. In this recipe, we will look at how to use standard manipulators to write and read monetary values.

Getting ready

You should now be familiar with locales and how to set them for a stream. This topic was discussed in the Using localized settings for streams recipe. It is recommended that you read that recipe before continuing.

The manipulators discussed in this recipe are available in the std namespace, in the <iomanip> header.

How to do it...

To write a monetary value to an output stream, you should do the following:

  • Set the desired locale for controlling the monetary format:
    std::cout.imbue(std::locale("en_GB.utf8"));
    
  • Use either a long double or a std::basic_string value for the amount...

Using time I/O manipulators

Similar to the monetary I/O manipulators that we discussed in the previous recipe, the C++11 standard provides manipulators that control the writing and reading of time values to and from streams, where time values are represented in the form of a std::tm object that holds a calendar date and time. In this recipe, you will learn how to use these time manipulators.

Getting ready

Time values used by the time I/O manipulators are expressed in std::tm values. You should be familiar with this structure from the <ctime> header.

You should also be familiar with locales and how to set them for a stream. This topic was discussed in the Using localized settings for streams recipe. It is recommended that you read that recipe before continuing.

The manipulators discussed in this recipe are available in the std namespace, in the <iomanip> header.

How to do it...

To write a time value to an output stream, you should perform the following...

Working with filesystem paths

An important addition to the C++17 standard is the filesystem library, which enables us to work with paths, files, and directories in hierarchical filesystems (such as Windows or POSIX filesystems). This standard library has been developed based on the boost.filesystem library. In the next few recipes, we will explore those features of the library that enable us to perform operations with files and directories, such as creating, moving, or deleting them, but also querying properties and searching. It is important, however, to first look at how this library handles paths.

Getting ready

For this recipe, we will consider most of the examples using Windows paths. In the accompanying code, all examples have both Windows and POSIX alternatives.

The filesystem library is available in the std::filesystem namespace, in the <filesystem> header. To simplify the code, we will use the following namespace alias in all of the examples:

namespace...

Creating, copying, and deleting files and directories

Operations with files, such as copying, moving, and deleting, or with directories, such as creating, renaming, and deleting, are all supported by the filesystem library. Files and directories are identified using a path (which can be absolute, canonical, or relative), a topic that was covered in the previous recipes. In this recipe, we will look at what the standard functions for the previously mentioned operations are and how they work.

Getting ready

Before going forward, you should read the Working with filesystem paths recipe. The introductory notes from that recipe also apply here. However, all of the examples in this recipe are platform-independent.

For all of the following examples, we will use the following variables and assume the current path is C:\Users\Marius\Documents on Windows and /home/marius/docs for a POSIX system:

auto err = std::error_code{};
auto basepath = fs::current_path();
auto path = basepath...

Removing content from a file

Operations such as copying, renaming, moving, or deleting files are directly provided by the filesystem library. However, when it comes to removing content from a file, you must perform explicit actions.

Regardless of whether you need to do this for text or binary files, you could implement the following pattern:

  1. Create a temporary file.
  2. Copy only the content that you want from the original file to the temporary file.
  3. Delete the original file.
  4. Rename/move the temporary file to the name/location of the original file.

In this recipe, we will learn how to implement this pattern for a text file.

Getting ready

For the purpose of this recipe, we will consider removing empty lines, or lines that start with a semicolon (;), from a text file. For this example, we will have an initial file, called sample.dat, that contains the names of Shakespeare’s plays, but also empty lines and lines that start with a semicolon...

Checking the properties of an existing file or directory

The filesystem library provides functions and types that enable developers to check for the existence of a filesystem object, such as a file or directory, its properties, such as the type (the file, directory, symbolic link, and more), the last write time, permissions, and more. In this recipe, we will look at what these types and functions are and how they can be used.

Getting ready

For the following code samples, we will use the namespace alias fs for the std::filesystem namespace. The filesystem library is available in the header with the same name, <filesystem>. Also, we will use the variables shown here, path for the path of a file, and err for receiving potential operating system error codes from the filesystem APIs:

auto path = fs::current_path() / "main.cpp";
auto err = std::error_code{};

Also, the function to_time_t shown here will be referred to in this recipe:

  template <typename...

Enumerating the content of a directory

So far in this chapter, we have looked at many of the functionalities provided by the filesystem library, such as working with paths, performing operations with files and directories (creating, moving, renaming, deleting, and so on), and querying or modifying properties. Another useful functionality when working with the filesystem is to iterate through the content of a directory. The filesystem library provides two directory iterators, one called directory_iterator, which iterates the content of a directory, and one called recursive_directory_iterator, which recursively iterates the content of a directory and its subdirectories. In this recipe, we will learn how to use them.

Getting ready

For this recipe, we will consider a directory with the following structure:

test/
├──data/
│ ├──input.dat
│ └──output.dat
├──file_1.txt
├─...

Finding a file

In the previous recipe, we learned how to use directory_iterator and recursive_directory_iterator to enumerate the content of a directory. Displaying the content of a directory, as we did in the previous recipe, is only one of the scenarios in which this is needed. The other major scenario is when searching for particular entries in a directory, such as files with a particular name, extension, and so on. In this recipe, we will demonstrate how we can use the directory iterators and the iterating patterns shown earlier to find files that match a given criterion.

Getting ready

You should read the previous recipe, Enumerating the content of a directory, for details about directory iterators. In this recipe, we will also use the same test directory structure that was presented in the previous recipe.

How to do it...

To find files that match particular criteria, use the following pattern:

  1. Use recursive_directory_iterator to iterate through all...
lock icon
The rest of the chapter is locked
You have been reading a chapter from
Modern C++ Programming Cookbook - Third Edition
Published in: Feb 2024Publisher: PacktISBN-13: 9781835080542
Register for a free Packt account to unlock a world of extra content!
A free Packt account unlocks extra newsletters, articles, discounted offers, and much more. Start advancing your knowledge today.
undefined
Unlock this book and the full library FREE for 7 days
Get unlimited access to 7000+ expert-authored eBooks and videos courses covering every tech area you can think of
Renews at €14.99/month. Cancel anytime

Author (1)

author image
Marius Bancila

Marius Bancila is a software engineer with two decades of experience in developing solutions for line of business applications and more. He is the author of The Modern C++ Challenge and Template Metaprogramming with C++. He works as a software architect and is focused on Microsoft technologies, mainly developing desktop applications with C++ and C#. He is passionate about sharing his technical expertise with others and, for that reason, he has been recognized as a Microsoft MVP for C++ and later developer technologies since 2006. Marius lives in Romania and is active in various online communities.
Read more about Marius Bancila