Companion code · Chapter 14

Resource Ownership: RAII, the Rules of Zero, Three, and Five, and Smart Pointers

10 files. About this chapter.

Download ch14.zip

ch14/buffer5.hpp40 lines
// buffer5.hpp: stn::Buffer with the rule of five
#pragma once
#include <algorithm>
#include <cstddef>
#include <utility>

namespace stn {
class Buffer {
public:
    explicit Buffer(std::size_t n)
        : data_(new double[n]{}), size_(n) {}
    ~Buffer() { delete[] data_; }                   // 1
    Buffer(const Buffer& other)                     // 2
        : data_(new double[other.size_]), size_(other.size_) {
        std::copy(other.data_, other.data_ + size_, data_);
    }
    Buffer& operator=(const Buffer& other) {        // 3
        Buffer tmp(other);      // copy first; may throw
        swap(tmp);              // then swap; cannot throw
        return *this;           // tmp frees the old array
    }
    Buffer(Buffer&& other) noexcept                 // 4
        : data_(std::exchange(other.data_, nullptr)),
          size_(std::exchange(other.size_, 0)) {}
    Buffer& operator=(Buffer&& other) noexcept {    // 5
        Buffer tmp(std::move(other));
        swap(tmp);
        return *this;
    }
    void swap(Buffer& other) noexcept {
        std::swap(data_, other.data_);
        std::swap(size_, other.size_);
    }
    double& operator[](std::size_t i) { return data_[i]; }
    std::size_t size() const { return size_; }
private:
    double* data_;      // owner of the heap array, or nullptr
    std::size_t size_;
};
}  // namespace stn
ch14/buffer5_test.cpp25 lines
// buffer5_test.cpp: exercise copy, self-assignment and move
#include "buffer5.hpp"
#include <cstdio>
#include <utility>

int main() {
    stn::Buffer a(4);
    a[0] = 21.75;
    stn::Buffer b = a;              // copy ctor: deep copy
    b[0] = -3.5;
    std::printf("a[0]=%.2f b[0]=%.2f\n", a[0], b[0]);

    stn::Buffer& same = a;
    a = same;                       // self-assignment via alias
    std::printf("after a = same: a[0]=%.2f size=%zu\n",
                a[0], a.size());

    stn::Buffer c = std::move(a);   // move ctor: steal
    std::printf("c[0]=%.2f c.size=%zu a.size=%zu\n",
                c[0], c.size(), a.size());

    b = std::move(c);               // move assign
    std::printf("b[0]=%.2f c.size=%zu\n", b[0], c.size());
    return 0;
}
ch14/copy_vs_move.cpp35 lines
// copy_vs_move.cpp: time a deep copy against a move
#include "buffer5.hpp"
#include <chrono>
#include <cstdio>
#include <utility>

using Clock = std::chrono::steady_clock;

static double us_since(Clock::time_point t0) {
    return std::chrono::duration<double, std::micro>(Clock::now() - t0)
        .count();
}

int main() {
    const int reps = 20;
    std::printf("%10s %12s %12s\n", "doubles", "copy (us)", "move (us)");
    for (std::size_t n = 1000; n <= 10000000; n *= 10) {
        stn::Buffer src(n);
        src[n - 1] = 1.0;
        double copy_us = 0.0, move_us = 0.0, check = 0.0;
        for (int r = 0; r < reps; ++r) {
            auto t0 = Clock::now();
            stn::Buffer c = src;                 // deep copy
            copy_us += us_since(t0);
            t0 = Clock::now();
            stn::Buffer m = std::move(c);        // pointer steal
            move_us += us_since(t0);
            check += m[n - 1];                   // keep the work alive
        }
        std::printf("%10zu %12.2f %12.3f\n", n, copy_us / reps,
                    move_us / reps);
        if (check != reps) return 1;
    }
    return 0;
}
ch14/cycle_weak.cpp44 lines
// cycle_weak.cpp: a weak_ptr back-pointer breaks the cycle
#include <cstdio>
#include <memory>
#include <vector>

struct Station;

struct Sensor {
    int id;
    std::weak_ptr<Station> home;      // observes; does not own
    explicit Sensor(int i) : id(i) {}
    ~Sensor() { std::printf("~Sensor %d\n", id); }
    void report() const {
        if (auto st = home.lock()) {  // a shared_ptr, or empty
            std::printf("sensor %d: station alive\n", id);
        } else {
            std::printf("sensor %d: station gone\n", id);
        }
    }
};

struct Station {
    std::vector<std::shared_ptr<Sensor>> sensors;
    ~Station() { std::printf("~Station\n"); }
};

std::shared_ptr<Sensor> build_station() {
    auto st = std::make_shared<Station>();
    for (int id = 1001; id <= 1002; ++id) {
        auto s = std::make_shared<Sensor>(id);
        s->home = st;                    // sensor -> station
        st->sensors.push_back(s);        // station -> sensor
    }
    std::printf("station use_count = %ld\n", st.use_count());
    st->sensors[0]->report();
    return st->sensors[0];          // caller shares sensor 1001
}                                   // st dies; count drops to 0

int main() {
    auto kept = build_station();
    std::printf("back in main\n");
    kept->report();
    return 0;
}
ch14/mixed_delete.cpp26 lines
// mixed_delete.cpp: a raw delete on memory a unique_ptr owns
#include <cstdint>
#include <cstdio>
#include <memory>

namespace stn {
struct Reading {
    std::uint32_t sensor_id;
    std::int64_t time_s;
    double value;
    std::uint8_t flags;
};
}  // namespace stn

void print_and_release(stn::Reading* r) {  // agent's helper
    std::printf("%u at %lld: %.2f\n", r->sensor_id,
                static_cast<long long>(r->time_s), r->value);
    delete r;                         // "clean up when done"
}

int main() {
    auto r = std::make_unique<stn::Reading>(
        stn::Reading{1001, 60, 21.75, 0x01});
    print_and_release(r.get());
    return 0;                    // ~unique_ptr deletes again
}
ch14/observer.cpp20 lines
// observer.cpp: owners and a raw observer
#include <cstdio>
#include <memory>
#include <utility>
#include <vector>

struct Sensor { int id; double last; };

double latest(const Sensor* s) {         // observer
    return s ? s->last : 0.0;            // never deletes
}

int main() {
    std::vector<std::unique_ptr<Sensor>> owned;  // the owners
    auto s = std::make_unique<Sensor>(Sensor{1001, 21.75});
    owned.push_back(std::move(s));
    const Sensor* watch = owned[0].get();        // borrow
    std::printf("%.2f\n", latest(watch));        // 21.75
    return 0;
}
ch14/reading_log.hpp40 lines
// reading_log.hpp: the agent-written class to audit
#pragma once
#include <cstdint>
#include <cstring>
namespace stn {
struct Reading { std::uint32_t sensor_id; std::int64_t time_s;
                 double value; std::uint8_t flags; };
class ReadingLog {
public:
    ReadingLog(const char* name, std::size_t cap)
        : name_(new char[std::strlen(name) + 1]),
          items_(new Reading[cap]), count_(0), cap_(cap) {
        std::strcpy(name_, name);
    }
    ~ReadingLog() { delete[] name_; delete items_; }
    ReadingLog(const ReadingLog& o)
        : name_(o.name_), items_(new Reading[o.cap_]),
          count_(o.count_), cap_(o.cap_) {
        for (std::size_t i = 0; i < count_; ++i)
            items_[i] = o.items_[i];
    }
    ReadingLog& operator=(const ReadingLog& o) {
        delete[] items_;
        items_ = new Reading[o.cap_];
        for (std::size_t i = 0; i < o.count_; ++i)
            items_[i] = o.items_[i];
        count_ = o.count_; cap_ = o.cap_;
        return *this;
    }
    bool add(const Reading& r) {
        if (count_ == cap_) return false;
        items_[count_++] = r; return true;
    }
    std::size_t count() const { return count_; }
    const Reading& at(std::size_t i) const { return items_[i]; }
private:
    char* name_; Reading* items_;
    std::size_t count_, cap_;
};
}  // namespace stn
ch14/self_assign_bad.cpp34 lines
// self_assign_bad.cpp: a copy assignment that frees first
#include <algorithm>
#include <cstddef>
#include <cstdio>

namespace stn {
class Buffer {
public:
    explicit Buffer(std::size_t n)
        : data_(new double[n]{}), size_(n) {}
    ~Buffer() { delete[] data_; }
    Buffer& operator=(const Buffer& other) {
        delete[] data_;               // free the old array
        double* fresh = new double[other.size_];
        std::copy(other.data_, other.data_ + other.size_, fresh);
        data_ = fresh;
        size_ = other.size_;
        return *this;
    }
    double& operator[](std::size_t i) { return data_[i]; }
private:
    double* data_;
    std::size_t size_;
};
}  // namespace stn

int main() {
    stn::Buffer a(4);
    a[0] = 21.75;
    stn::Buffer& same = a;
    a = same;                       // self-assignment via alias
    std::printf("a[0] = %.2f\n", a[0]);
    return 0;
}
ch14/shared_cost.cpp39 lines
// shared_cost.cpp: a shared_ptr by value against a raw observer
#include <chrono>
#include <cstdio>
#include <memory>

struct Sensor { double last = 21.75; };

__attribute__((noinline))
double by_shared(std::shared_ptr<Sensor> s) {
    return s->last;             // the copy: count +1, then -1
}
__attribute__((noinline))
double by_observer(const Sensor* s) {
    return s->last;             // just an address
}

template <typename F>
double ns_per_call(F f) {
    const int n = 10000000;
    double sum = 0.0;
    auto t0 = std::chrono::steady_clock::now();
    for (int i = 0; i < n; ++i) sum += f();
    std::chrono::duration<double, std::nano> d =
        std::chrono::steady_clock::now() - t0;
    if (sum < 0) std::printf("impossible\n");
    return d.count() / n;
}

int main() {
    std::printf("sizeof unique_ptr = %zu, shared_ptr = %zu\n",
                sizeof(std::unique_ptr<Sensor>),
                sizeof(std::shared_ptr<Sensor>));
    auto sp = std::make_shared<Sensor>();
    std::printf("shared_ptr by value: %.2f ns/call\n",
                ns_per_call([&] { return by_shared(sp); }));
    std::printf("raw observer:        %.2f ns/call\n",
                ns_per_call([&] { return by_observer(sp.get()); }));
    return 0;
}
ch14/shared_cost_mt.cpp41 lines
// shared_cost_mt.cpp: shared_cost.cpp after one empty thread
#include <chrono>
#include <cstdio>
#include <memory>

struct Sensor { double last = 21.75; };

__attribute__((noinline))
double by_shared(std::shared_ptr<Sensor> s) {
    return s->last;             // the copy: count +1, then -1
}
__attribute__((noinline))
double by_observer(const Sensor* s) {
    return s->last;             // just an address
}

template <typename F>
double ns_per_call(F f) {
    const int n = 10000000;
    double sum = 0.0;
    auto t0 = std::chrono::steady_clock::now();
    for (int i = 0; i < n; ++i) sum += f();
    std::chrono::duration<double, std::nano> d =
        std::chrono::steady_clock::now() - t0;
    if (sum < 0) std::printf("impossible\n");
    return d.count() / n;
}

#include <thread>
int main() {
    std::thread([]{}).join();
    std::printf("sizeof unique_ptr = %zu, shared_ptr = %zu\n",
                sizeof(std::unique_ptr<Sensor>),
                sizeof(std::shared_ptr<Sensor>));
    auto sp = std::make_shared<Sensor>();
    std::printf("shared_ptr by value: %.2f ns/call\n",
                ns_per_call([&] { return by_shared(sp); }));
    std::printf("raw observer:        %.2f ns/call\n",
                ns_per_call([&] { return by_observer(sp.get()); }));
    return 0;
}