2025-03-02

vak: (Знайка)
Продолжаем изучать примитивы языка Rust. В этом деле Грок неплохой помощник. Как выясняется, тип Mutex<T> представляет собой удобную комбинацию классов std::mutex и std::unique_lock из Си++.

Question: Please show me a possible implementation of type Mutex<T> in C++.

I'll provide an implementation of a Mutex<T> type in C++ that mimics Rust's Mutex. Rust's Mutex provides mutual exclusion with a lock guard pattern, so we'll create a similar construct in C++ using the standard library's threading primitives.

Here's a possible implementation: mutex.cpp.
#include <mutex>
#include <memory>

template<typename T>
class Mutex {
private:
T data;
std::mutex mtx;

public:
// Constructor
explicit Mutex(T value) : data(std::move(value)) {}

// Lock guard class (similar to Rust's MutexGuard)
class LockGuard {
private:
T& data_ref;
std::unique_lock<std::mutex> lock;

public:
LockGuard(T& d, std::mutex& m)
: data_ref(d), lock(m) {}

// Access the underlying data
T& operator*() { return data_ref; }
T* operator->() { return &data_ref; }
};

// Lock method that returns the guard
LockGuard lock() {
return LockGuard(data, mtx);
}

// Prevent copying
Mutex(const Mutex&) = delete;
Mutex& operator=(const Mutex&) = delete;

// Allow move construction
Mutex(Mutex&&) = default;
Mutex& operator=(Mutex&&) = default;
};
vak: (Знайка)
Тип Mutex<T> хорош, но разрешает доступ нескольким потокам только по очереди. Что если нам чаще требуется только читать объект, а изменять только изредка? Через RwLock<T> можно получить множественный доступ на чтение.

Question: Please show a meaningful C++ implementation of type RwLock<T>.

I'll create a C++ implementation of RwLock<T> similar to Rust's RwLock, which provides a reader-writer lock allowing multiple readers or one writer. We'll use C++'s std::shared_mutex (available since C++17) as the underlying synchronization primitive.

Here's the implementation: rwlock.cpp.
#include <shared_mutex>
#include <memory>

template<typename T>
class RwLock {
private:
T data;
mutable std::shared_mutex mtx; // mutable to allow locking in const methods

public:
// Constructor
explicit RwLock(T value) : data(std::move(value)) {}

// Read guard class (multiple readers allowed)
class ReadGuard {
private:
const T& data_ref;
std::shared_lock<std::shared_mutex> lock;

public:
ReadGuard(const T& d, std::shared_mutex& m)
: data_ref(d), lock(m) {}

const T& operator*() const { return data_ref; }
const T* operator->() const { return &data_ref; }
};

// Write guard class (exclusive writer)
class WriteGuard {
private:
T& data_ref;
std::unique_lock<std::shared_mutex> lock;

public:
WriteGuard(T& d, std::shared_mutex& m)
: data_ref(d), lock(m) {}

T& operator*() { return data_ref; }
T* operator->() { return &data_ref; }
};

// Lock methods
ReadGuard read_lock() const {
return ReadGuard(data, mtx);
}

WriteGuard write_lock() {
return WriteGuard(data, mtx);
}

// Prevent copying
RwLock(const RwLock&) = delete;
RwLock& operator=(const RwLock&) = delete;

// Allow move construction
RwLock(RwLock&&) = default;
RwLock& operator=(RwLock&&) = default;
};
объяснения )
vak: (Знайка)
Концепция copy-on-write не представлена в традиционных языках программирования, поэтому вызывает трудности у народа. Тип Cow<T> в Rust часто используется со строками, чтобы избежать лишнего клонирования. Грок покажет нам упрощённую реализацию.

Question: Please show a possible implementation of Rust's type std::borrow::Cow in C++.

I'll show you how to implement a simplified version of Rust's std::borrow::Cow (Copy-on-Write) in C++. Rust's Cow is an enum that can hold either a borrowed reference (&'a T) or an owned value (T), typically used to avoid unnecessary cloning. In C++, we can approximate this using a combination of templates, variants, and smart pointers.

Here's a possible implementation: cow.cpp.
#include <variant>
#include <memory>
#include <string>
#include <stdexcept>

// A simplified Cow implementation in C++
template<typename T>
class Cow {
private:
// Using variant to hold either a borrowed pointer or an owned value
std::variant<const T*, std::unique_ptr<T>> data;

public:
// Constructor for borrowed value
explicit Cow(const T* borrowed) : data(borrowed) {
if (!borrowed) {
throw std::invalid_argument("Borrowed pointer cannot be null");
}
}

// Constructor for owned value
explicit Cow(T&& owned) : data(std::make_unique<T>(std::move(owned))) {}

// Copy constructor
Cow(const Cow& other) {
if (std::holds_alternative<const T*>(other.data)) {
data = std::get<const T*>(other.data);
} else {
const auto& owned = std::get<std::unique_ptr<T>>(other.data);
data = std::make_unique<T>(*owned);
}
}

// Move constructor
Cow(Cow&& other) noexcept = default;

// Assignment operators
Cow& operator=(const Cow& other) {
if (this != &other) {
Cow temp(other);
swap(*this, temp);
}
return *this;
}

Cow& operator=(Cow&& other) noexcept = default;

// Access the underlying value (non-mutating)
const T& operator*() const {
if (std::holds_alternative<const T*>(data)) {
return *std::get<const T*>(data);
}
return *std::get<std::unique_ptr<T>>(data);
}

const T* operator->() const {
return &(**this);
}

// Check if the Cow holds a borrowed value
bool is_borrowed() const {
return std::holds_alternative<const T*>(data);
}

// Check if the Cow holds an owned value
bool is_owned() const {
return std::holds_alternative<std::unique_ptr<T>>(data);
}

// Convert to owned value if borrowed (clone on write)
void to_owned() {
if (is_borrowed()) {
data = std::make_unique<T>(*std::get<const T*>(data));
}
}

// Swap implementation
friend void swap(Cow& lhs, Cow& rhs) noexcept {
using std::swap;
swap(lhs.data, rhs.data);
}
};