ch15/bench.cpp64 lines
// bench.cpp: direct vs virtual calls. Build with -O2, no sanitizers.
#include <chrono>
#include <cstdio>
#include <memory>
#include <random>
#include <vector>
struct Sensor {
virtual ~Sensor() = default;
virtual double calib(double x) const { return x * 1.0001; }
[[gnu::noinline]] double direct(double x) const {
return x * 1.0001;
}
double inlined(double x) const { return x * 1.0001; }
};
struct Temp : Sensor {
double calib(double x) const override { return x * 0.9999; }
};
struct Hum : Sensor {
double calib(double x) const override { return x * 1.0002; }
};
struct Wind : Sensor {
double calib(double x) const override { return x * 0.9998; }
};
using Ptrs = std::vector<std::unique_ptr<Sensor>>;
template <class Call>
void time_it(const char* name, long n, Call call) {
double best = 1e9, sink = 0;
for (int rep = 0; rep < 7; ++rep) { // keep the best of 7
auto t0 = std::chrono::steady_clock::now();
for (long i = 0; i < n; ++i) sink += call(i);
std::chrono::duration<double> d =
std::chrono::steady_clock::now() - t0;
if (d.count() < best) best = d.count();
}
std::printf("%-20s %5.2f ns/call (%g)\n", name,
best / n * 1e9, sink);
}
int main() {
const long n = 10'000'000;
Ptrs same, mixed; // four sensors each
for (int k = 0; k < 4; ++k) same.push_back(std::make_unique<Temp>());
mixed.push_back(std::make_unique<Temp>());
mixed.push_back(std::make_unique<Hum>());
mixed.push_back(std::make_unique<Wind>());
mixed.push_back(std::make_unique<Sensor>());
std::mt19937 rng(42);
std::vector<unsigned char> pick(n); // random sensor order
for (auto& p : pick) p = static_cast<unsigned char>(rng() % 4);
std::vector<unsigned char> cycle(n); // 0,1,2,3,0,1,2,3,...
for (long i = 0; i < n; ++i) cycle[i] = static_cast<unsigned char>(i % 4);
auto& s = same;
auto& m = mixed;
time_it("inlined", n, [&](long i) { return s[pick[i]]->inlined(i); });
time_it("direct", n, [&](long i) { return s[pick[i]]->direct(i); });
time_it("virtual, one type", n,
[&](long i) { return s[pick[i]]->calib(i); });
time_it("virtual, 4 cycled", n,
[&](long i) { return m[cycle[i]]->calib(i); });
time_it("virtual, 4 random", n,
[&](long i) { return m[pick[i]]->calib(i); });
}
ch15/compose.cpp40 lines
// compose.cpp: calibration as a part, not a parent
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
#include "sensor.hpp"
namespace stn {
struct Calibration { // a small value type
double scale = 1.0;
double offset = 0.0;
double apply(double r) const { return r * scale + offset; }
};
struct Channel { // has a sensor, has a
std::unique_ptr<Sensor> sensor; // calibration
Calibration cal;
};
class Station {
public:
void add(std::unique_ptr<Sensor> s, Calibration c = {}) {
channels_.push_back(Channel{std::move(s), c});
}
void recalibrate(std::size_t i, Calibration c) {
channels_.at(i).cal = c;
}
double read(std::size_t i, std::int64_t t_s) {
Channel& ch = channels_.at(i);
return ch.cal.apply(ch.sensor->sample(t_s));
}
private:
std::vector<Channel> channels_; // Station owns channels
};
} // namespace stn
int main() {
stn::Station st;
st.add(std::make_unique<stn::TemperatureSensor>(1001));
std::cout << "before: " << st.read(0, 21600) << '\n';
st.recalibrate(0, {1.0, -0.4}); // field correction
std::cout << "after: " << st.read(0, 21600) << '\n';
}
ch15/dispatch.cpp28 lines
// dispatch.cpp: a hidden function vs. an overridden one
#include <iostream>
#include <string>
class Sensor {
public:
virtual ~Sensor() = default;
std::string label() const { return "sensor"; } // plain
virtual std::string unit() const { return "?"; } // virtual
};
class TemperatureSensor : public Sensor {
public:
std::string label() const { return "temp"; } // hides
std::string unit() const override { return "C"; }
};
void describe(const Sensor& s) {
std::cout << "via Sensor&: " << s.label() << ' ' << s.unit()
<< '\n';
}
int main() {
TemperatureSensor t;
std::cout << "direct: " << t.label() << ' ' << t.unit()
<< '\n';
describe(t);
}
ch15/err_override.cpp11 lines
// err_override.cpp: a signature mismatch caught by override
#include "sensor.hpp"
namespace stn {
class HumiditySensor : public Sensor {
public:
using Sensor::Sensor;
std::string_view unit() const override { return "%RH"; }
double sample(int t_s) override { return 60.0 + t_s % 30; }
};
}
int main() {}
ch15/novdtor.cpp37 lines
// novdtor.cpp: delete through a base pointer, no virtual
// destructor. Undefined behavior.
#include <cstdint>
#include <iostream>
#include <vector>
namespace stn {
class Sensor {
public:
explicit Sensor(std::uint32_t id) : id_{id} {}
~Sensor() { std::cout << "~Sensor " << id_ << '\n'; }
virtual double sample(std::int64_t t) = 0;
private:
std::uint32_t id_;
};
class WindSensor : public Sensor {
public:
explicit WindSensor(std::uint32_t id)
: Sensor{id}, gusts_(1440, 0.0) {} // a day of gusts
~WindSensor() { std::cout << "~WindSensor\n"; }
double sample(std::int64_t t) override {
gusts_[static_cast<std::size_t>(t % 1440)] = 3.5;
return 3.5;
}
private:
std::vector<double> gusts_; // 11,520 heap bytes
};
} // namespace stn
int main() {
stn::Sensor* s = new stn::WindSensor{1004};
std::cout << "wind: " << s->sample(60) << " m/s\n";
delete s; // which destructor?
}
ch15/opening.cpp36 lines
// opening.cpp: the agent's first PA5 draft, trimmed (illustrative)
#include <cstdint>
#include <iostream>
#include <memory>
#include <vector>
namespace stn {
class Sensor {
public:
explicit Sensor(std::uint32_t id) : id_{id} {}
~Sensor() = default; // "unique_ptr cleans up"
std::uint32_t id() const { return id_; }
virtual double sample(std::int64_t t_s) = 0;
private:
std::uint32_t id_;
};
class WindSensor : public Sensor {
public:
explicit WindSensor(std::uint32_t id)
: Sensor{id}, gusts_(1440, 0.0) {}
double sample(std::int64_t t_s) override {
gusts_[static_cast<std::size_t>(t_s % 1440)] = 3.5;
return 3.5;
}
private:
std::vector<double> gusts_;
};
} // namespace stn
int main() {
std::vector<std::unique_ptr<stn::Sensor>> sensors;
sensors.push_back(std::make_unique<stn::WindSensor>(1004));
for (auto& s : sensors) {
std::cout << s->id() << ',' << s->sample(60) << '\n';
}
}
ch15/predict.cpp29 lines
// predict.cpp: which function runs?
#include <iostream>
#include <string>
class Sensor {
public:
Sensor() { std::cout << "A " << unit() << '\n'; }
virtual ~Sensor() = default;
virtual std::string unit() const { return "?"; }
std::string kind() const { return "generic"; }
void report() const {
std::cout << "D " << kind() << ' ' << unit() << '\n';
}
};
class PressureSensor : public Sensor {
public:
std::string unit() const override { return "hPa"; }
std::string kind() const { return "pressure"; }
};
int main() {
PressureSensor ps;
Sensor* p = &ps;
std::cout << "B " << p->unit() << '\n';
std::cout << "C " << p->kind() << '\n';
p->report();
std::cout << "E " << p->Sensor::unit() << '\n';
}
ch15/proportion.cpp23 lines
// proportion.cpp: what else a station step costs (companion repo only)
#include <chrono>
#include <cmath>
#include <cstdio>
int main() {
const long n = 2'000'000;
char buf[64];
double sink = 0;
for (int rep = 0; rep < 3; ++rep) {
auto t0 = std::chrono::steady_clock::now();
for (long i = 0; i < n; ++i) {
int len = std::snprintf(buf, sizeof buf, "%u,%ld,%.2f,%d\n", 1001u, i * 60, 21.75 + i * 1e-6, 3);
sink += buf[len - 2];
}
std::chrono::duration<double> d = std::chrono::steady_clock::now() - t0;
std::printf("snprintf csv line: %.1f ns\n", d.count() / n * 1e9);
t0 = std::chrono::steady_clock::now();
for (long i = 0; i < n; ++i) sink += std::sin(i * 1e-3);
d = std::chrono::steady_clock::now() - t0;
std::printf("std::sin: %.1f ns\n", d.count() / n * 1e9);
}
std::printf("%g\n", sink);
}
ch15/sensor.hpp40 lines
// sensor.hpp: PA5 sensor hierarchy (two of the four kinds)
#pragma once
#include <cmath>
#include <cstdint>
#include <numbers>
#include <string_view>
namespace stn {
class Sensor { // abstract base
public:
explicit Sensor(std::uint32_t id) : id_{id} {}
virtual ~Sensor() = default; // virtual dtor
Sensor(const Sensor&) = delete; // no copies, so
Sensor& operator=(const Sensor&) = delete; // no slicing
std::uint32_t id() const { return id_; }
virtual std::string_view unit() const = 0; // pure virtual
virtual double sample(std::int64_t t_s) = 0;
private:
std::uint32_t id_;
};
class TemperatureSensor final : public Sensor {
public:
using Sensor::Sensor;
std::string_view unit() const override { return "C"; }
double sample(std::int64_t t_s) override { // daily swing
return 12.0 + 6.0 * std::sin(2.0 * std::numbers::pi
* t_s / 86400.0);
}
};
class WindSensor final : public Sensor {
public:
using Sensor::Sensor;
std::string_view unit() const override { return "m/s"; }
double sample(std::int64_t t_s) override {
return 3.0 + static_cast<double>(t_s % 300) / 100.0;
}
};
} // namespace stn
ch15/slicing.cpp36 lines
// slicing.cpp: copying a derived object into a base object
#include <cstdint>
#include <iostream>
#include <vector>
class Sensor { // concrete, copyable base
public:
virtual ~Sensor() = default;
virtual const char* unit() const { return "?"; }
virtual double sample(std::int64_t) { return 0.0; }
};
class TemperatureSensor : public Sensor {
public:
explicit TemperatureSensor(double off) : offset_{off} {}
const char* unit() const override { return "C"; }
double sample(std::int64_t t) override {
return 20.0 + t / 600.0 + offset_;
}
private:
double offset_; // calibration offset
};
void show(const char* w, Sensor& s) {
std::cout << w << s.sample(600) << ' ' << s.unit() << '\n';
}
void log_by_value(Sensor s) { show("by value: ", s); }
int main() {
TemperatureSensor t{-0.25};
std::vector<Sensor> sensors;
sensors.push_back(t); // copies the Sensor part
show("original: ", t);
show("in vector: ", sensors[0]);
log_by_value(t); // copies the Sensor part
std::cout << "sizes: " << sizeof t << " vs "
<< sizeof(Sensor) << '\n';
}
ch15/station.cpp37 lines
// station.cpp: a Station owns its sensors and steps time
#include <iomanip>
#include <iostream>
#include <memory>
#include <utility>
#include <vector>
#include "sensor.hpp"
namespace stn {
class Station {
public:
void add(std::unique_ptr<Sensor> s) {
sensors_.push_back(std::move(s));
}
void step(std::int64_t t_s) { // one simulated minute
for (const auto& s : sensors_) { // borrow; never copy
std::cout << s->id() << ',' << t_s << ','
<< s->sample(t_s) << ",1 (" << s->unit()
<< ")\n";
}
}
std::size_t size() const { return sensors_.size(); }
private:
std::vector<std::unique_ptr<Sensor>> sensors_; // owner
};
} // namespace stn
int main() {
stn::Station st;
st.add(std::make_unique<stn::TemperatureSensor>(1001));
st.add(std::make_unique<stn::WindSensor>(1004));
std::cout << std::fixed << std::setprecision(2);
for (std::int64_t t = 0; t <= 120; t += 60) st.step(t);
std::cout << st.size() << " sensors\n";
}