Я обнаружил действительно странную ошибку в кодовой базе, над которой я работал, которую я только недавно смог выделить и создать что-то воспроизводимое.Ошибка заключается в том, что улов внутри simulate_container_init
пропускается, когда throw_threshold
четный.throw_threshold
используется в ex_trigger
для имитации выброса объектов при копировании в ситуациях, таких как создание контейнера или присвоение для целей модульного тестирования.
Первоначально я думал, что это ошибка компилятора MSVC (в 14.16 во времяэто написание), но после успешного воспроизведения его в GCC 7.1 и Clang 3.9.1 (чуть ли не старейшем из всех, которые поддерживают мой пример), я не уверен, что с ним делать больше, так как код кажется правильным и функционирует правильнокогда throw_threshold
нечетно.
#include <cstdint>
#include <atomic>
#include <memory>
#include <random>
#include <iostream>
#include <exception>
#include <type_traits>
// Used to trigger an exception after a number of constructions have occurred.
struct ex_trigger
{
private:
static std::atomic<uint32_t> ctor_count;
static std::atomic<uint32_t> throw_threshold;
public:
static inline void set_throw_threshold(uint32_t value) noexcept
{
throw_threshold.store(value, std::memory_order_release);
}
std::atomic<uint32_t> value;
inline ex_trigger(const ex_trigger& source) :
value(source.value.load(std::memory_order_acquire))
{
if (ctor_count.load(std::memory_order_acquire) >=
throw_threshold.load(std::memory_order_acquire)) {
throw std::logic_error("test");
}
ctor_count.fetch_add(1);
}
inline ex_trigger(uint32_t value) noexcept :
value(value)
{
ctor_count.fetch_add(1);
}
};
std::atomic<uint32_t> ex_trigger::ctor_count;
std::atomic<uint32_t> ex_trigger::throw_threshold;
// Simulates the construction of a container by copying an initializer list.
template<class T>
inline void simulate_container_ctor(std::initializer_list<T> values) {
// Intentionally leaked to simplify this example.
// Alignment of T is completely ignored for simplicity.
auto sim_data = static_cast<T*>(
::operator new(sizeof(T) * values.size()));
for (auto value : values) {
if constexpr (std::is_nothrow_copy_constructible_v<T>) {
new (sim_data++) T(value);
} else {
try {
new (sim_data++) T(value);
} catch (...) {
// Placeholder for cleanup code which is sometimes skipped.
std::cout << "caught [inner]\n";
throw;
}
}
}
}
int main()
{
// The "inner" catch handler within simulate_container_init will be skipped when the argument
// to set_throw_threshold is even, but otherwise appears to work correctly when it's odd. Note
// that the argument must be between 10-20 for an exception to be triggered in this example.
ex_trigger::set_throw_threshold(11);
try {
simulate_container_ctor({
ex_trigger(1),
ex_trigger(2),
ex_trigger(3),
ex_trigger(4),
ex_trigger(5),
ex_trigger(6),
ex_trigger(7),
ex_trigger(8),
ex_trigger(9),
ex_trigger(10)
});
} catch (const std::logic_error&) {
std::cout << "caught [outer]\n";
}
std::cin.get();
return 0;
}
Когда throw_threshold
является четным, вывод (неверно):
catch [external]
Когда throw_threshold
равностранно, результат (как и ожидалось):
catch [inner]
catch [external]
Я потратил бесчисленные часы на отладку и пробовал разные подходы, но, похоже, ячего-то не хватаетВсе, что поможет понять это, будет высоко ценится.