Companion code · Chapter 12

Moving to C++: Is It Really C++?

14 files. About this chapter.

Download ch12.zip

ch12/constref.cpp17 lines
#include <iostream>
#include <vector>

// Reads the readings; promises not to change them.
double max_value(const std::vector<double>& values) {
    double best = values.empty() ? 0.0 : values[0];
    for (double v : values)
        if (v > best) best = v;
    values[0] = best;      // a slip: writes through const&
    return best;
}

int main() {
    const std::vector<double> temps = {3.5, 1.5, 0.5, 2.5};
    std::cout << max_value(temps) << '\n';
    return 0;
}
ch12/habits.sh18 lines
#!/bin/sh
# habits.sh: first-pass scan for C habits in a C++ file.
# Usage: sh habits.sh file.cpp   (a hit is a lead, not proof)
f="$1"
scan() {  # $1 = label, $2 = extended regex (GNU grep)
    hits=$(grep -nE "$2" "$f")
    [ -n "$hits" ] && printf '[%s]\n%s\n' "$1" "$hits"
}
scan "malloc/free"   '\b(malloc|calloc|realloc|free)\s*\('
scan "raw array" '\b(char|int|long|double|\w+_t)\s+\w+\s*\['
scan "char string"   '\bchar\s*\*|\bchar\s+\w+\s*\['
scan "printf family" '\b(s?n?printf|puts|scanf)\s*\('
scan "C string call" '\bstr(n?cpy|n?cat|len|n?cmp|dup)\s*\('
scan "length param"  '\*\s*\w+,\s*(size_t|int)\s+\w+\)'
scan "#define const" '^\s*#define\s+[A-Z_]+\s+\S'
g++ -std=c++20 -fsyntax-only -Wold-style-cast "$f" 2>&1 |
    grep -o '[0-9]*:[0-9]*: warning: use of old-style.*' |
    sed 's/^/[C cast] /'
ch12/label.cpp20 lines
#include <cstdio>
#include <iostream>
#include <string>

std::string make_label(const std::string& station, int id) {
    std::string label = station + "/" + std::to_string(id);
    if (id == 1001) label += " (temperature)";
    return label;
}

int main() {
    std::string a = make_label("north-field", 1001);
    std::string b = a;              // copies the characters
    b[0] = 'N';                     // changes b only
    std::cout << a << " | " << b << '\n';
    std::cout << a.size() << " chars, equal? "
              << std::boolalpha << (a == b) << '\n';
    std::printf("for a C API: %s\n", a.c_str());
    return 0;
}
ch12/malloc_str.cpp20 lines
#include <cstdlib>
#include <iostream>
#include <string>

struct SensorInfo {
    unsigned id;
    std::string name;      // a C++ object with its own rules
};

int main() {
    std::size_t n = 2;
    SensorInfo* s =
        static_cast<SensorInfo*>(std::malloc(n * sizeof *s));
    if (s == nullptr) return 1;
    s[0].id = 1001;
    s[0].name = "air temperature, north mast";  // crash?
    std::cout << s[0].id << ' ' << s[0].name << '\n';
    std::free(s);
    return 0;
}
ch12/newdel.cpp21 lines
#include <cstddef>
#include <iostream>

struct Reading {
    unsigned sensor_id;
    long time_s;
    double value;
};

int main() {
    std::size_t n = 4;
    Reading* one = new Reading{1001, 0, 21.5};   // one object
    double* temps = new double[n];               // array of n
    for (std::size_t i = 0; i < n; i++) temps[i] = 20.0 + i;

    std::cout << one->value << ' ' << temps[n - 1] << '\n';

    delete one;          // pairs with new
    delete temps;        // BUG: new[] must pair with delete[]
    return 0;
}
ch12/printf_str.cpp10 lines
#include <cstdio>
#include <string>
#include <vector>

int main() {
    std::string label = "north-field/1001";
    std::vector<double> temps = {3.5, 1.5, 0.5, 2.5};
    std::printf("%s: %zu readings\n", label, temps.size());
    return 0;
}
ch12/ref.cpp34 lines
#include <cstdint>
#include <iostream>

struct Reading {
    std::uint32_t sensor_id;
    std::int64_t time_s;
    double value;
};

void calibrate_ptr(Reading* p, double offset) {
    if (p == nullptr) return;  // a pointer can be null
    p->value += offset;
}

void calibrate_ref(Reading& r, double offset) {
    r.value += offset;         // r always names an object
}

int main() {
    Reading t = {1001, 60, 21.50};
    Reading* p = &t;           // p holds t's address
    Reading& ref = t;          // ref is another name for t

    calibrate_ptr(p, 0.25);
    calibrate_ref(ref, 0.25);
    calibrate_ref(t, 0.25);    // no & at the call site
    std::cout << "value  " << t.value << '\n';
    std::cout << "&t     " << &t << '\n';
    std::cout << "p      " << p << "  &p " << &p << '\n';
    std::cout << "&ref   " << &ref << '\n';
    std::cout << "sizeof(p) " << sizeof(p)
              << "  sizeof(ref) " << sizeof(ref) << '\n';
    return 0;
}
ch12/seam.cpp134 lines
#include <algorithm>
#include <chrono>
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

namespace stn {

struct Image {
    std::size_t width = 0;
    std::size_t height = 0;
    std::vector<std::uint8_t> pixels;  // row-major, w * h
};

Image make_image(std::size_t w, std::size_t h) {
    return Image{w, h, std::vector<std::uint8_t>(w * h, 0)};
}

std::uint8_t pixel(const Image& img, std::size_t r,
                   std::size_t c) {
    return img.pixels[r * img.width + c];
}

void set_pixel(Image& img, std::size_t r, std::size_t c,
               std::uint8_t value) {
    img.pixels[r * img.width + c] = value;
}

// Edge energy: how sharply brightness changes at (r, c).
int energy(const Image& img, std::size_t r, std::size_t c) {
    std::size_t left = c > 0 ? c - 1 : c;
    std::size_t right = c + 1 < img.width ? c + 1 : c;
    std::size_t up = r > 0 ? r - 1 : r;
    std::size_t down = r + 1 < img.height ? r + 1 : r;
    int dx = pixel(img, r, right) - pixel(img, r, left);
    int dy = pixel(img, down, c) - pixel(img, up, c);
    return std::abs(dx) + std::abs(dy);
}

// Naive: cheapest seam cost from (r, c) to the bottom row.
long seam_from(const Image& img, std::size_t r, std::size_t c,
               long& calls) {
    calls++;
    long e = energy(img, r, c);
    if (r + 1 == img.height) return e;      // bottom row
    std::size_t lo = c > 0 ? c - 1 : c;
    std::size_t hi = c + 1 < img.width ? c + 1 : c;
    long best = LONG_MAX;
    for (std::size_t k = lo; k <= hi; k++) {
        long sub = seam_from(img, r + 1, k, calls);
        best = std::min(best, sub);
    }
    return e + best;
}

// Memoized: the same recursion; each (r, c) is solved once.
long seam_memo(const Image& img, std::size_t r, std::size_t c,
               std::vector<long>& memo, long& calls) {
    calls++;
    long& slot = memo[r * img.width + c];   // a reference
    if (slot >= 0) return slot;             // solved
    long e = energy(img, r, c);
    if (r + 1 == img.height) return slot = e;  // bottom
    std::size_t lo = c > 0 ? c - 1 : c;
    std::size_t hi = c + 1 < img.width ? c + 1 : c;
    long best = LONG_MAX;
    for (std::size_t k = lo; k <= hi; k++) {
        long sub = seam_memo(img, r + 1, k, memo, calls);
        best = std::min(best, sub);
    }
    return slot = e + best;
}

long min_seam_naive(const Image& img, long& calls) {
    long best = LONG_MAX;
    for (std::size_t c = 0; c < img.width; c++)
        best = std::min(best, seam_from(img, 0, c, calls));
    return best;
}

long min_seam_memo(const Image& img, long& calls) {
    // -1 marks a cell that is not solved yet.
    std::vector<long> memo(img.width * img.height, -1);
    long best = LONG_MAX;
    for (std::size_t c = 0; c < img.width; c++) {
        long cost = seam_memo(img, 0, c, memo, calls);
        best = std::min(best, cost);
    }
    return best;
}

}  // namespace stn

// Synthetic sky-camera frame: sky fading down, a dark mast.
stn::Image test_frame(std::size_t w, std::size_t h) {
    stn::Image img = stn::make_image(w, h);
    for (std::size_t r = 0; r < h; r++)
        for (std::size_t c = 0; c < w; c++) {
            int v = 220 - static_cast<int>(6 * r) +
                    static_cast<int>((r * 31 + c * 17) % 13);
            if (c == w / 3) v = 40;         // the mast
            auto byte = static_cast<std::uint8_t>(v);
            stn::set_pixel(img, r, c, byte);
        }
    return img;
}

int main(int argc, char** argv) {
    using ms = std::chrono::duration<double, std::milli>;
    using clk = std::chrono::steady_clock;
    std::size_t max_h = argc > 1 ? std::stoul(argv[1]) : 16;
    std::cout << " h  naive calls  naive ms  memo calls"
              << "  memo ms\n"
              << std::fixed << std::setprecision(3);
    for (std::size_t h = 4; h <= max_h; h += 2) {
        stn::Image img = test_frame(10, h);  // width 10
        long nc = 0, mc = 0;
        auto t0 = clk::now();
        long a = stn::min_seam_naive(img, nc);
        auto t1 = clk::now();
        long b = stn::min_seam_memo(img, mc);
        ms naive = t1 - t0, memo = clk::now() - t1;
        std::cout << std::setw(2) << h << std::setw(13) << nc
                  << std::setw(10) << naive.count()
                  << std::setw(12) << mc
                  << std::setw(9) << memo.count()
                  << (a == b ? "" : "  MISMATCH") << '\n';
    }
    return 0;
}
ch12/stn_print.cpp30 lines
#include <cstdint>
#include <iostream>
#include <vector>

namespace stn {

struct Reading {
    std::uint32_t sensor_id;
    std::int64_t time_s;
    double value;
};

void print(const Reading& r) {
    std::cout << r.sensor_id << " v=" << r.value << '\n';
}

void print(const std::vector<Reading>& rs) {
    std::cout << rs.size() << " readings\n";
    for (const Reading& r : rs) print(r);  // first overload
}

}  // namespace stn

int main() {
    std::vector<stn::Reading> rs = {{1001, 0, 21.5},
                                    {1002, 0, 88.0}};
    stn::print(rs[0]);
    stn::print(rs);
    return 0;
}
ch12/summary.c39 lines
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define MAX_NAME 32
#define FROST_C 2.0

struct reading {
    uint32_t sensor_id;
    int64_t time_s;
    double value;
};

double stn_mean(const struct reading *r, size_t n) {
    double sum = 0.0;
    for (size_t i = 0; i < n; i++) sum += r[i].value;
    return n > 0 ? sum / (double)n : 0.0;
}

int main(void) {
    char name[MAX_NAME];
    snprintf(name, sizeof name, "%s", "north-field");
    double temps[] = {3.5, 1.5, 0.5, 2.5};
    size_t n = sizeof temps / sizeof temps[0];
    struct reading *r = malloc(n * sizeof *r);
    if (r == NULL) return 1;
    int below = 0;
    for (size_t i = 0; i < n; i++) {
        r[i].sensor_id = 1001;
        r[i].time_s = 60 * (int64_t)i;
        r[i].value = temps[i];
        if (r[i].value < FROST_C) below++;
    }
    printf("%s: mean %.2f C over %zu readings\n",
           name, stn_mean(r, n), n);
    printf("below frost line: %d\n", below);
    free(r);
    return 0;
}
ch12/summary.cpp40 lines
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <iostream>

#define MAX_NAME 32
#define FROST_C 2.0
struct reading {
    uint32_t sensor_id;
    int64_t time_s;
    double value;
};

double stn_mean(const reading *r, size_t n) {
    double sum = 0.0;
    for (size_t i = 0; i < n; i++) sum += r[i].value;
    return n > 0 ? sum / (double)n : 0.0;
}

int main() {
    char name[MAX_NAME];
    std::strcpy(name, "north-field");
    double temps[] = {3.5, 1.5, 0.5, 2.5};
    size_t n = sizeof temps / sizeof temps[0];
    reading *r = (reading *)malloc(n * sizeof(reading));
    if (r == NULL) return 1;
    int below = 0;
    for (size_t i = 0; i < n; i++) {
        r[i].sensor_id = 1001;
        r[i].time_s = 60 * (int64_t)i;
        r[i].value = temps[i];
        if (r[i].value < FROST_C) below++;
    }
    std::cout << name << ": mean " << stn_mean(r, n)
              << " C over " << n << " readings\n";
    printf("below frost line: %d\n", below);
    free(r);
    return 0;
}
ch12/summary_modern.cpp40 lines
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <string>
#include <vector>

namespace stn {

constexpr double frost_line_c = 2.0;

struct Reading {
    std::uint32_t sensor_id;
    std::int64_t time_s;
    double value;
};

double mean(const std::vector<Reading>& rs) {
    double sum = 0.0;
    for (const Reading& r : rs) sum += r.value;
    if (rs.empty()) return 0.0;
    return sum / static_cast<double>(rs.size());
}
}  // namespace stn

int main() {
    const std::string name = "north-field";
    const std::vector<double> temps = {3.5, 1.5, 0.5, 2.5};
    std::vector<stn::Reading> rs;
    for (std::size_t i = 0; i < temps.size(); i++)
        rs.push_back({1001, 60 * static_cast<std::int64_t>(i),
                      temps[i]});
    int below = 0;
    for (const stn::Reading& r : rs)
        if (r.value < stn::frost_line_c) below++;
    std::cout << std::fixed << std::setprecision(2) << name
              << ": mean " << stn::mean(rs) << " C over "
              << rs.size() << " readings\n"
              << "below frost line: " << below << '\n';
    return 0;
}
ch12/vec.cpp25 lines
#include <cstddef>
#include <iostream>
#include <vector>

double mean(const std::vector<double>& v) {
    double sum = 0.0;
    for (double x : v) sum += x;
    if (v.empty()) return 0.0;
    return sum / static_cast<double>(v.size());
}

int main() {
    std::vector<double> temps;            // empty: size 0
    for (int minute = 0; minute < 6; minute++) {
        temps.push_back(20.0 + 0.5 * minute);
        std::cout << "size " << temps.size()
                  << "  capacity " << temps.capacity()
                  << '\n';
    }
    std::cout << "mean " << mean(temps) << '\n';
    std::cout << "temps[2] " << temps[2] << '\n';
    std::cout << "temps.at(9) " << std::flush;
    std::cout << temps.at(9) << '\n';  // checked access
    return 0;
}
ch12/vec_oob.cpp12 lines
#include <string>
#include <iostream>
#include <vector>

int main(int argc, char** argv) {
    std::vector<double> temps;
    for (int minute = 0; minute < 6; minute++)
        temps.push_back(20.0 + 0.5 * minute);
    std::size_t i = argc > 1 ? std::stoul(argv[1]) : 0;
    std::cout << "temps[" << i << "] = " << temps[i] << '\n';
    return 0;
}