Jez Higgins

Freelance software grandad
software created
extended or repaired


Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations

Hire me
Contact

Older posts are available in the archive or through tags.

Feed

Friday 05 June 2020 The Forest Road Reader, No 2.48 : STinC++ - makecopy

STinC++ rewrites the programs in Software Tools in Pascal using C++

Deep into chapter 3, we’re now on our fifth file handling program and it takes a bit of a turn away from what we’ve done so far.

PROGRAM

makecopy copy a file to a new file

USAGE

makecopy old new

FUNCTION

makecopy copies the file old to a new instance of the file new, i.e. if new already exists it is truncated and rewritten, otherwise it is made to exist. The new file is an exact replica of the old.

EXAMPLE

To make a backup copy of a precious file

makecopy precious backup

BUGS

Copying a file onto itself is very system dependent and usually disastrous.

As you read this program description, you’re probably already sketching the source out in your head. The description even says 'truncated and rewritten', which is a pretty solid example of implementation detail leaking into documentation. I certainly was, not least because Kernighan and Plauger have established a pattern of setting out a problem at the start of a chapter, and then growing and tweaking the code we write to take us through solving the next problem and the next.

This program differs significantly from its predecessors, though. It’s entirely about the file system. We’re copying a file. We’re not changing the contents, we’re not even looking at it. We just need to manipulate the file, and if we can do that without cracking it open and messing around with its insides, well that would be lovely. And we can! In a development that’s taken nearly 20 years and required what seems like unreasonable amounts of high-quality brainpower, as of the 2017 standard C++ sports a spiffy filesystem library.

makecopy.cpp
#include <iostream>
#include <filesystem>
#include <tuple>
#include "../../lib/arguments.hpp"

namespace fs = std::filesystem;

std::tuple<fs::path, fs::path> file_paths(int argc, char const* argv[]);

int main(int argc, char const* argv[]) {
  try {
    auto [source, destination] = file_paths(argc, argv);

    if (fs::exists(destination) && fs::is_regular_file(destination))
      fs::remove(destination); (1)

    fs::copy_file(source, destination);
  } catch (const std::exception& fse) {
    std::cerr << fse.what() << '\n';
  }
}

std::tuple<fs::path, fs::path> file_paths(int argc, char const* argv[]) {
  auto filenames = stiX::make_arguments(argc, argv);

  if (filenames.size() != 2)
    throw std::runtime_error("Error: makecopy old new");

  auto source = fs::path(filenames[0]);
  auto destination = fs::path(filenames[1]);

  if (fs::equivalent(source, destination)) (2)
    throw std::runtime_error("Error: source and destination are the same file");

  return std::make_tuple(source, destination); (3)
} // file_paths
  1. The copy operation, fs::copy_file, fails if the destination exists, hence why I delete any existing file first.

  2. fs::equivalent is almost, but not quite, equals for paths. Two paths are equivalent if they point to the same file, even if one is, say, a file and the other a symlink, or one is a relative path and the other absolute, or whatever. If, underneath it all, they resolve to the same file, they are equivalent.

  3. I’m using a std::tuple as an ad hoc multivalue return. Using the magic of auto and structured binding, we can unpack the tuple directly into two separate variables at the call site and never really see the tuple at all. As I’m returning two values, I could have used a std::pair I suppose, but std::tuple just feels more suited here. To me, a pair says these two things belong together while tuple says these are just some things that I happen have right now.

But does it work?

It does, but you’ll have to build and run it yourself to prove it.

Partly because it’s so short, little more than a glorified wrapper around a library function, and partly because it’s all about filesystem manipulation, this is the first STinC++ program for which I wrote no tests. I could, I suppose, have shimmed out the std::filesystem functions I needed, done a bit of namespace manipulation, and thrown some tests around it but, what, really would that prove? There’s no 'business logic' to validate, no unit to isolate. I could try to test 'good' and 'bad' inputs I suppose, but filesystems are hard and any mock couldn’t hope to match real behaviour beyond the simplest.

Part of the raison d’etre of a standard library is to provide guarantees about the boundaries of your program. I’m happy to rely on those here. After all, this version doesn’t suffer from the bug described by Kernighan and Plauger and can make a much stronger claim on the new file is an exact replica of the old than their program could.

That’s All Very Neat, But Haven’t It Rather Missed The Point

Having thoroughly covered reading files, Kernighan and Plauger’s pedagogic intent with makecopy was to introduce programmatic file creation. This version of makecopy does not do that. It’s not even close. I’m a little surprised to get this far into the book before the Pascal and C++ version diverged so completely. Maybe we haven’t come as far as we thought.

Source code

Source code for this program, indeed the whole project, is available in the stiX GitHub repository. print is fifth program of Chapter 3.

Library Reference

  • The Filesystem library was added in C++17. It does look largely as you expect, and is one of the many Standard features born and nutured in Boost

  • Structured Binding Declaration, binding the specified names to subobjects or elements of the initializer, is one of the gifts of C++17 that will surely be giving for a long, long time.

  • std::tuple has been around since C++11, but I wouldn’t be surprised if it had largely passed you by until structured binding made it radically more convenient to use.

Endnotes

This whole endeavour relies Software Tools in Pascal and Software Tools, both by Brian W Kernighan and PJ Plauger. I love these books and commend them to you. They’re both still in print, but new copies are, frankly, just ridiculously expensive. Happily, here are plenty of second-hand copies floating round, or you can borrow them from the The Internet Archive using the links above.

For this project I’ve been using JetBrain’s CLion, which I liked enough to buy a license. CLion uses CMake to build projects. My previous flirtations with CMake, admittedly many years ago, weren’t a huge success. Not so this time - it’s easy to use and works a treat.


Tagged code, and software-tools-in-c++


Jez Higgins

Freelance software grandad
software created
extended or repaired

Follow me on Mastodon
Applications, Libraries, Code
Talks & Presentations

Hire me
Contact

Older posts are available in the archive or through tags.

Feed