Проинициализировать что-либо потокобезопасно один раз.
std::unique_ptr<Display> display;
void print(const std::string& message)
{
if (!display)
display.reset(new Display());
display->print(message);
}
std::unique_ptr<Display> display;
static std::once_flag displayInitFlag;
void print(const std::string& message)
{
std::call_once(displayInitFlag,
[]() { display.reset(new Display()); });
display->print(message);
}
Хранилище уровня потока (с++11).
- Создается когда запускается поток
- Уничтожается когда поток завершает работу
- Для каждого потока свое
static thread_local std::map<std::string, int> threadCache;
n философов сидят за столом, перед каждым тарелка с едой, между тарелками лежит по 1 палочке. Чтобы поесть, надо взять 2 палочки, то есть философ должен взять палочку слева и справа от своей тарелки.
- Философы не вежливые
Философ взял правую палочку и не отдает, левую палочку взял сосед и тоже не отдает - произошла взаимная блокировка (deadlock).
- Философы вежливые
Философ взял правую палочку, посмотрел, что левая занята соседом, положил правую, подумал, повторил с начала. Остальные поступают аналогично. Если все думают одинаковое время, то никто из них не сможет поесть (livelock).
- Философы слишком быстро едят
Поэтому палочка либо слева, либо справа все время занята и философ голодает (starvation).
- Иерархия ресурсов
Надо пронумеровать палочки, далее философы берут палочку с наименьшим номером, затем с наибольшим, возращают палочки в обратном порядке. Если n - 1 философов взяли палочке, то останется одна с наибольшим номером, поэтому ее последний философ взять не сможет. Один из взявших палочку берет палочку с наибольшим номером, затем возвращает ее, позволяя поесть следующему.
- Официант
По запросу выдает палочки или предлагает подождать, если все палочки заняты.
#include <cassert>
#include <condition_variable>
#include <mutex>
#include <thread>
#include <vector>
class Steward
{
public:
explicit Steward(size_t maxPhilosophers)
: maxPhilosophers_(maxPhilosophers)
, busyChopsticks_(maxPhilosophers)
{
}
using Chopsticks = std::pair<size_t, size_t>;
Chopsticks getChopsticks(size_t philosopher)
{
assert(philosopher < maxPhilosophers_);
const auto chopsticks = getChopstickNumbers(philosopher);
std::unique_lock<std::mutex> lock(mutex_);
while (busyChopsticks_[chopsticks.first] == true
|| busyChopsticks_[chopsticks.second] == true)
{
chopsticksAreBack_.wait(lock);
}
busyChopsticks_[chopsticks.first] = true;
busyChopsticks_[chopsticks.second] = true;
return chopsticks;
}
void returnChopsticks(Chopsticks chopsticks)
{
{
std::lock_guard<std::mutex> lock(mutex_);
busyChopsticks_[chopsticks.first] = false;
busyChopsticks_[chopsticks.second] = false;
}
chopsticksAreBack_.notify_all();
}
private:
const size_t maxPhilosophers_;
std::vector<bool> busyChopsticks_;
std::mutex mutex_;
std::condition_variable chopsticksAreBack_;
Chopsticks getChopstickNumbers(size_t philosopher)
{
// | P0 | P1 | P2 |
// 0 1 2 3
return std::make_pair(philosopher, philosopher + 1);
}
};
int main()
{
const size_t maxPhilosophers = 300;
Steward steward(maxPhilosophers);
std::vector<std::thread> philosophers;
for (size_t i = 0; i < maxPhilosophers; ++i)
{
philosophers.emplace_back(
[i, &steward]()
{
const auto chopsticks = steward.getChopsticks(i);
// eat
steward.returnChopsticks(chopsticks);
});
}
for (auto& t : philosophers)
{
t.join();
}
return 0;
}
#include <mutex>
#include <thread>
#include <vector>
int main()
{
const size_t maxPhilosophers = 300;
using Chopstick = std::mutex;
std::vector<Chopstick> chopsticks(maxPhilosophers + 1);
std::vector<std::thread> philosophers;
for (size_t i = 0; i < maxPhilosophers; ++i)
{
philosophers.emplace_back(
[i, &chopsticks]()
{
const auto left = i;
std::lock_guard<Chopstick> leftLock(chopsticks[left]);
const auto right = i + 1;
std::lock_guard<Chopstick> rightLock(chopsticks[right]);
// eat
});
}
for (auto& t : philosophers)
{
t.join();
}
return 0;
}
Цель - парикмахер должен работать когда клиенты есть и спать, когда их нет. Клиент прийдя в парикмахерскую, если парикмахер свободен стрижется, если занят, то идет в приемную и там садится на стул и ждет, если свободных стульев нет, то уходит.
Producer–consumer problem - частный случай этой задачи.
struct Client {};
std::mutex barbershop;
std::condition_variable hasClient;
const size_t ChairsNum = 5;
std::queue<Client> clients;
bool clientCame(Client c)
{
{
std::unique_lock<std::mutex> lock(barbershop);
if (clients.size() == ChairsNum)
return false;
clients.push(c);
}
hasClient.notify_one();
return true;
}
void barberThread()
{
while (true)
{
Client c;
{
std::unique_lock<std::mutex> lock(barbershop);
while (clients.empty())
{
hasClient.wait(lock);
}
c = clients.front();
clients.pop();
}
trim(c);
}
}
template <class T>
class queue
{
bool empty() const;
T pop();
};
queue<Task> tasks;
if (!tasks.empty())
process(tasks.pop());
template <class T>
class ThreadSafeQueue
{
bool tryPop(T& value)
{
std::lock_guard<std::mutex> lock(mutex);
if (data_.empty())
return false;
value = data_.front();
data_.pop();
return true;
}
};
ThreadSafeQueue<Task> tasks;
Task task;
if (tasks.tryPop(task))
process(task);
#include <mutex>
#include <unordered_map>
template <class Key, class Value>
class ThreadSafeCache
{
std::unordered_map<Key, Value> cache_;
std::mutex mutex_;
public:
void put(const Key& key, const Value& val)
{
std::lock_guard<std::mutex> lock(mutex_);
cache_.insert_or_assign(key, val);
}
void put(const Key& key, Value&& val)
{
std::lock_guard<std::mutex> lock(mutex_);
cache_.insert_or_assign(key, val);
}
template <class... Args>
void emplace(const Key& key, Args&&... args)
{
std::lock_guard<std::mutex> lock(mutex_);
cache_.emplace(std::forward<Args>(args)...);
}
bool get(const Key& key, Value& val)
{
std::lock_guard<std::mutex> lock(mutex_);
const auto it = cache_.find(key);
if (it == cache_.end())
{
return false;
}
val = it->second;
return true;
}
void erase(const Key& key)
{
std::lock_guard<std::mutex> lock(mutex_);
cache_.erase(key);
}
};
Потоки желающие прочесть данные все равно выстраиваются в очередь на чтение, хотя чтение не требует такой синхронизации
Есть область памяти, позволяющая чтение и запись. Несколько потоков имеют к ней доступ, при этом одновременно могут читать сколько угодно потоков, но писать — только один. Как обеспечить такой режим доступа?
- Приоритет читателя
Пока память открыта на чтение, давать читателям беспрепятственный доступ. Писатели могут ждать сколько угодно.
- Приоритет писателя
Как только появился хоть один писатель, читателей больше не пускать. При этом читатели могут простаивать.
- Одинаковый приоритет
Независимо от действий других потоков, читатель или писатель должен пройти барьер за конечное время.
Голодание (starvation) — это более высокоуровневая проблема, чем гонки и взаимоблокировки. Материал для любознательных: стратегии планирования задач
#include <shared_mutex>
std::shared_mutex mutex;
void reader()
{
std::shared_lock<std::shared_mutex> lock(mutex);
// блокируется если есть unique_lock
// не блокируется, если есть другие shared_lock
}
void writer()
{
std::shared_lock<std::shared_mutex> lock(mutex);
// получить эксклюзивный доступ на общих условиях
}
#include <shared_mutex>
#include <unordered_map>
template <class Key, class Value>
class ThreadSafeCache
{
std::unordered_map<Key, Value> cache_;
std::shared_mutex mutex_;
public:
void put(const Key& key, const Value& val)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
cache_.insert_or_assign(key, val);
}
void put(const Key& key, Value&& val)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
cache_.insert_or_assign(key, val);
}
template <class... Args>
void emplace(const Key& key, Args&&... args)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
cache_.emplace(std::forward<Args>(args)...);
}
bool get(const Key& key, Value& val)
{
std::shared_lock<std::shared_mutex> lock(mutex_);
const auto it = cache_.find(key);
if (it == cache_.end())
{
return false;
}
val = it->second;
return true;
}
void erase(const Key& key)
{
std::unique_lock<std::shared_mutex> lock(mutex_);
cache_.erase(key);
}
};
Linux (4 core) | MacOS X (2 core) | Windows (4 core) | |
---|---|---|---|
Generating data | 0.573 | 0.929 | 0.545 |
Mutex | 1.495 | 90.487 | 1.018 |
Shared mutex | 2.746 | 177.496 | 0.630 |
std::atomic атомарно изменяет значение, гарантия, что изменение не приводит к блокировке есть только у atomic_flag
// флаг сброшен
std::atomic_flag keepWorking = ATOMIC_FLAG_INIT;
// устанавливает флаг и возвращает предыдущее значение
keepWorking.test_and_set();
void threadFunc()
{
while (keepWorking.test_and_set(std::memory_order_acquire)))
{
}
}
// сбросить флаг
keepWorking.clear(std::memory_order_release);
std::atomic<int> i;
i.is_lock_free(); // true если изменение не приводит к блокировке
i.store(5);
int j = i.exchange(7); // возвращает предыдущее значение
int k = 7;
bool ok = i.compare_exchange_weak(k, 9);
// если i == k, то i станет равным 9 и вернется true, иначе в k будет
// записано актуальное значение и вернется false
ok = i.compare_exchange_strong(k, 9);
// аналогично compare_exchange_weak, но не бывает ложных срабатываний
// как если бы *this != expected
while (!i.compare_exchange_weak(k, 9)) ;
// compare_exchange_weak можно использовать в цикле, на некоторых архитектурах
// это дает более эффективный код
std::atomic<int> i;
int prev = i.fetch_add(5);
// fetch_sub
// fetch_and
// fetch_or
// fetch_xor
mutex, condition_variable и аналогичные примитивы вызывают блокировку потока
class Spinlock
{
std::atomic_flag lock_;
public:
Spinlock()
: lock_(ATOMIC_FLAG_INIT)
{
}
void lock()
{
while (lock_.test_and_set()) ;
}
void unlock()
{
lock_.clear();
}
};
Spinlock не блокирует поток, но приводит к блокировкам других потоков
#include <mutex>
#include <stack>
template <class T>
class ThreadSafeStdStack
{
std::stack<T> stack_;
std::mutex mutex_;
public:
void push(const T& val)
{
std::lock_guard<std::mutex> lock(mutex_);
stack_.push(val);
}
bool pop(T& val)
{
std::lock_guard<std::mutex> lock(mutex_);
if (stack_.empty())
{
return false;
}
val = stack_.top();
stack_.pop();
return true;
}
};
head: [value, prev] -> [value, prev] -> [value, nullptr]
#include <mutex>
template <class T>
class ThreadSafeStack
{
struct Node
{
T val_;
Node* prev_;
};
Node* head_ = nullptr;
std::mutex mutex_;
public:
~ThreadSafeStack()
{
T val;
while (pop(val));
}
void push(const T& val)
{
std::lock_guard<std::mutex> lock(mutex_);
auto newNode = new Node { val, head_ };
head_ = newNode;
}
bool pop(T& val)
{
std::lock_guard<std::mutex> lock(mutex_);
if (!head_)
{
return false;
}
val = head_->val_;
auto toDelete = head_;
head_ = head_->prev_;
delete toDelete;
return true;
}
};
#include <atomic>
template <class T>
class LockFreeStack
{
struct Node
{
T val_;
Node* prev_;
};
std::atomic<Node*> head_ = nullptr;
public:
~LockFreeStack()
{
T val;
while (pop(val));
}
void push(const T& val)
{
auto newNode = new Node { val, head_.load() };
while (!head_.compare_exchange_weak(newNode->prev_, newNode));
}
bool pop(T& val)
{
auto node = head_.load();
while (node && !head_.compare_exchange_weak(node, node->prev_));
if (!node)
{
return false;
}
val = node->val_;
return true;
}
};
Linux (4 core) | MacOS X (2 core) | Windows (4 core) | |
---|---|---|---|
Generating data | 0.197 | 0.210 | 0.190 |
Std mutex | 3.550 | 98.250 | 0.647 |
Mutex | 9.188 | 101.779 | 1.531 |
Lock free | 1.962 | 1.069 | 1.441 |
Проблемы реализации:
- Небезопасна с исключениями
- Утечка памяти
#include <atomic>
template <class T>
class LockFreeStack
{
struct Node
{
T val_;
Node* prev_;
};
std::atomic<Node*> head_ = nullptr;
std::atomic<int> threadsInPop = 0;
std::atomic<Node*> deleteLater_ = nullptr;
void cleanup()
{
auto toDelete = deleteLater_.load();
while (toDelete)
{
auto tmp = toDelete;
toDelete = tmp->prev_;
delete tmp;
}
deleteLater_ = nullptr;
}
public:
~LockFreeStack()
{
T val;
while (pop(val));
}
void push(const T& val)
{
auto newNode = new Node { val, head_.load() };
while (!head_.compare_exchange_weak(newNode->prev_, newNode));
}
bool pop(T& val)
{
++threadsInPop;
auto toDelete = head_.load();
while (toDelete && !head_.compare_exchange_weak(toDelete, toDelete->prev_));
if (!toDelete)
{
--threadsInPop;
return false;
}
val = toDelete->val_;
if (threadsInPop == 1)
{
delete toDelete;
cleanup();
}
else
{
toDelete->prev_ = deleteLater_.load();
while (!deleteLater_.compare_exchange_weak(toDelete->prev_, toDelete));
}
--threadsInPop;
return true;
}
};
Проблемы реализации:
- Голодание - если нагрузка большая, то в pop всегда будет несколько потоков, таким образом память не будет освобождаться
Реализация из книги "Параллельное программирование на С++" Энтони Уильямса, листинг 7.12
#include <atomic>
#include <memory>
template<typename T>
class lock_free_stack
{
private:
struct node;
struct counted_node_ptr
{
int external_count;
node* ptr;
};
struct node
{
std::shared_ptr<T> data;
std::atomic<int> internal_count;
counted_node_ptr next;
node(T const& data_):
data(std::make_shared<T>(data_)),
internal_count(0)
{}
};
std::atomic<counted_node_ptr> head;
void increase_head_count(counted_node_ptr& old_counter)
{
counted_node_ptr new_counter;
do
{
new_counter=old_counter;
++new_counter.external_count;
}
while(!head.compare_exchange_strong(
old_counter,new_counter,
std::memory_order_acquire,
std::memory_order_relaxed));
old_counter.external_count=new_counter.external_count;
}
public:
~lock_free_stack()
{
while(pop());
}
void push(T const& data)
{
counted_node_ptr new_node;
new_node.ptr=new node(data);
new_node.external_count=1;
new_node.ptr->next=head.load(std::memory_order_relaxed);
while(!head.compare_exchange_weak(
new_node.ptr->next,new_node,
std::memory_order_release,
std::memory_order_relaxed));
}
std::shared_ptr<T> pop()
{
counted_node_ptr old_head=
head.load(std::memory_order_relaxed);
for(;;)
{
increase_head_count(old_head);
node* const ptr=old_head.ptr;
if(!ptr)
{
return std::shared_ptr<T>();
}
if(head.compare_exchange_strong(
old_head,ptr->next,std::memory_order_relaxed))
{
std::shared_ptr<T> res;
res.swap(ptr->data);
int const count_increase=old_head.external_count-2;
if(ptr->internal_count.fetch_add(
count_increase,std::memory_order_release)==-count_increase)
{
delete ptr;
}
return res;
}
else if(ptr->internal_count.fetch_add(
-1,std::memory_order_relaxed)==1)
{
ptr->internal_count.load(std::memory_order_acquire);
delete ptr;
}
}
}
};
Протестировать не удалось, так как реализация в книге падала с Segmentation fault
Server srv("localhost", 8000);
while (true)
{
auto connection = srv.listen();
while (connection.good())
{
auto data = connection.read();
connection.write(data);
}
}
#include <boost/asio.hpp>
class server
{
boost::asio::ip::tcp::acceptor acceptor_;
public:
server(
boost::asio::io_context& io_context,
short port)
: acceptor_(
io_context,
boost::asio::ip::tcp::endpoint(
boost::asio::ip::tcp::v4(), port))
{
do_accept();
}
private:
void do_accept()
{
acceptor_.async_accept(
[this](
boost::system::error_code ec,
boost::asio::ip::tcp::socket socket)
{
if (!ec)
{
std::make_shared<session>(
std::move(socket))->start();
}
do_accept();
});
}
};
class session
: public std::enable_shared_from_this<session>
{
tcp::socket socket_;
const size_t max_length = 1024;
char data_[max_length];
public:
session(boost::asio::ip::tcp::socket socket)
: socket_(std::move(socket))
{
}
void start()
{
do_read();
}
private:
void do_read()
{
auto self(shared_from_this());
socket_.async_read_some(
boost::asio::buffer(data_, max_length),
[this, self](
boost::system::error_code ec,
std::size_t length)
{
if (!ec)
{
do_write(length);
}
});
}
void do_write(std::size_t length)
{
auto self(shared_from_this());
boost::asio::async_write(
socket_,
boost::asio::buffer(data_, length),
[this, self](
boost::system::error_code ec,
std::size_t /*length*/)
{
if (!ec)
{
do_read();
}
});
}
};
boost::asio::io_context io_context;
server s(io_context, 4000);
io_context.run();
[n][6][5][4][3][2][1][0]
Верхняя граница:
2^7 = 128
[0, 127]
Нижняя граница:
-(2^7 + 1)
[-128, 0]
-1 == 0b11111111
-128 == 0b10000000
uint8_t a = 255;
++a; // overflow
assert(a == 0);
--a; // underflow
assert(a == 255);
int8_t a = 127;
++a;
// ??? - UB
Иллюстрация неопределенного поведения:
#include <iostream>
int main()
{
for(int i = 0; i < 10; ++i)
{
auto val = i * 1000000000;
std::cout << i << ' ' << val << std::endl;
}
}
0 0
1 1000000000
2 2000000000
3 -1294967296
4 -294967296
5 705032704
6 1705032704
7 -1589934592
8 -589934592
9 410065408
10 1410065408
11 -1884901888
12 -884901888
...
Проверено на gcc -O2
int8_t a = -1;
uint32_t b = a;
assert(b == 4294967295);
Результат копирования знакового бита
uint32_t a = 10;
int32_t b = -1;
assert(a < b); // !!!
Если один из аргументов беззнаковый, то он неявно приводится к знаковому.
float f = 1.234e6;
Такая форма, в которой мантисса без учёта знака находится на интервале [1; 10)
−158,08 = −0,15808 * 10^3
0,00129 = −0,129 * 10^−2
0,15 = 0,15 * 10^0
Одинарная точность
32 бита
Порядок (exponent) 8 бит
Мантисса (significand) 23 бита
Можно представить целое до 2^24 без потери точности
Двойная точность
64 бита
Порядок (exponent) 11 бит
Мантисса (significand) 52 бита
Можно представить целое до 2^53 без потери точности
Возникает при переполнении или делении не нуля на ноль.
К операциям, приводящим к появлению NaN в качестве ответа, относятся:
- все математические операции, содержащие NaN в качестве одного из операндов;
- деление нуля на ноль;
- деление бесконечности на бесконечность;
- умножение нуля на бесконечность;
- сложение бесконечности с бесконечностью противоположного знака;
- вычисление квадратного корня отрицательного числа;
- логарифмирование отрицательного числа
NaN не равен ни одному другому значению (даже самому себе); соответственно, самый простой метод проверки результата на NaN — это сравнение полученной величины с самой собой.
Так как в нормализованной форме ноль невозможно представить единственным способом.
double a = 10 / 3.;
double b = a;
const bool isEqual = a == b; // Ok
double a = ...;
double b = ...;
// const bool isEqual = a == b; ???
const bool isEqual = fabs(a – b) < 0.0001;
Погрешность представления числа увеличивается с ростом самого этого числа
https://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
При сложении большого и малого чисел малое просто исчезнет
- Сортируем массив по возрастанию
- Получаем сумму от 0 до максимального положительного
- Получаем сумму от 0 до минимального отрицательного
- Складываем результаты 2 и 3
- Unicode – стандарт, а не кодировка
- CodePoint – символ в таблице Unicode (U+xxxx, где xxxx шестнадцетеричное число, например, U+0410)
- На данный момент уже существует 9 версия стандарта
- Начиная с версии 2.0 кодовая область расширена за пределы 2^16
- В версии 6.0 описано 109 242 графических и 273 прочих символа
H E L L O
U+0048 U+0065 U+006C U+006C U+006F
00 48 00 65 00 6C 00 6C 00 6F (Big Endian)
или
48 00 65 00 6C 00 6C 00 6F 00 (Little Endian)
EF BB BF
FE FF (Big Endian)
FF FE (Little Endian)
00 00 FE FF (Big Endian)
FF FE 00 00 (Little Endian)
- Позволяет отобразить 2^20 + 2^16 − 2048 (1 112 064) символов
- Кодирует 0000..D7FF и E000..10FFFF (в виде суррогатных пар)
- Исключенный диапазон D800..DFFF используется как раз для кодирования суррогатных пар
- Суррогатная пара – символ кодируемый двумя 16 битными словами
Переменное количество байт от 1 до 6 Позволяет закодировать 2^31 CodePoints
Диапазон символов | Количество байт |
---|---|
00000000-0000007F | 1 |
00000080-000007FF | 2 |
00000800-0000FFFF | 3 |
00010000-001FFFFF | 4 |
00200000-03FFFFFF | 5 |
04000000-7FFFFFFF | 6 |
- Кодирует любой символ 4 байтами
- Позволяет индексировать 231 CodePoints
- Символы Юникод непосредственно индексируемы
Графические символы в Юникоде подразделяются на протяжённые и непротяжённые (бесширинные). Непротяжённые символы при отображении не занимают места в строке. К ним относятся, в частности, знаки ударения и прочие диакритические знаки. Как протяжённые, так и непротяжённые символы имеют собственные коды. Протяжённые символы иначе называются базовыми (base characters), а непротяжённые — модифицирующими (combining characters); причём последние не могут встречаться самостоятельно. Например, символ «á» может быть представлен как последовательность базового символа «a» (U+0061) и модифицирующего символа « ́» (U+0301) или как монолитный символ «á» (U+00E1).
Поскольку одни и те же символы можно представить различными кодами, сравнение строк байт за байтом становится невозможным. Алгоритмы нормализации (normalization forms) решают эту проблему, выполняя приведение текста к определённому стандартному виду. Приведение осуществляется путём замены символов на эквивалентные с использованием таблиц и правил. «Декомпозицией» называется замена (разложение) одного символа на несколько составляющих символов, а «композицией», наоборот, — замена (соединение) нескольких составляющих символов на один символ.
В стандарте Юникода определены 4 алгоритма нормализации текста: NFD, NFC, NFKD и NFKC.
Например, двунаправленное письмо.
Стандарт Юникод поддерживает письменности языков как с направлением написания слева направо (left-to-right, LTR), так и с написанием справа налево (right-to-left, RTL) – например, арабское и еврейское письмо. В обоих случаях символы хранятся в «естественном» порядке; их отображение с учётом нужного направления письма обеспечивается приложением.
Кроме того, Юникод поддерживает комбинированные тексты, сочетающие фрагменты с разным направлением письма.
И многое другое...
Заменяет стандартное выделение памяти своей реализацией отслеживающей корректное ее использование и освобождение.
Позволяет определить:
- Попытки использования неинициализированной памяти
- Чтение/запись в память после её освобождения
- Чтение/запись за границами выделенного блока
- Утечки памяти
Используется для статического анализа кода. Возможности:
- Проверяет выход за пределы
- Обнаруживает утечки памяти
- Обнаруживает возможное разыменовывание NULL-указателей
- Обнаруживает неинициализированные переменные
- Обнаруживает неправильное использование STL
- Проверяет обработку исключительных ситуаций на безопасность
- Находит устаревшие и неиспользуемые функции
- Предупреждает о неиспользуемом или бесполезном коде
- Находит подозрительные участки кода, которые могут содержать в себе ошибки
[check.cpp:11]: (error) Array 'c[10]' index 10 out of bounds
[check.cpp:5]: (error) Memory leak: __p
[check.cpp:17]: (error) Memory leak: a
[check.cpp:14]: (error) Mismatching allocation and deallocation: A::__p
[check.cpp:8]: (error) Null pointer dereference
Документацию можно писать в коде. Doxygen проанализирует исходный код, построит документацию (html, latex и т.д.) из определений типов и дополнит ее найденными комментариями записанными в специальном формате:
/*!
Находит сумму двух чисел
@param a,b Складываемые числа
@return Сумму двух чисел, переданных в качестве аргументов
*/
double sum(const double a, const double b);
Если установлен Graphviz можно дополнить документацию графом вызовов и диаграммами наследования.
В разных единицах трансляции могут использоваться одни и те же заголовочные файлы – это приводит к многократной компиляции одного и того же кода.
Напрашивается идея собрать общие заголовочные файлы в один и скомпилировать их один раз – это предварительно откомпилированные заголовочные файлы (precompiled headers):
stdafx.h
#include <vector>
a.cpp
#include”stdafx.h” // Всегда первой строкой
#include “a.h”
...
При изменении stdafx.h или включенного в него заголовочного файла перекомпилируется весь проект!
Выполнить сортировку бинарного файла содержащего числа uint64_t в 2 потока. Доступно 8 Мб памяти, больше выделять нельзя ни явно, ни неявно (например, в виде контейнеров), при этом файл может быть размером превышающим доступную память. Пространство на диске можно считать бесконечным. Сортировку выполнять в новый файл.
EOF