Подробнее о странном поведении Интерфейс класса GMP C++ , связанного с ключевым словом C++ auto. Мой предыдущий вопрос также был об этом ключевом слове, и его удаление решило мою проблему. Однако это была проблема компиляции, а теперь проблема во время выполнения.
Простой тест ниже компилируется нормально, но создается дамп памяти.
#include <iostream>
#include <gmpxx.h>
auto test()
{
return mpz_class(2) * 2;
}
int main()
{
std::cout << test() << std::endl;
}
Если заменить ключевое слово auto на mpz_class, то тест пройдет корректно. Кроме того, если опустить флаг компиляции -O3, тест запустится, но выведет неправильное число, которое будет меняться при каждом выполнении (что обычно происходит, когда выполнение зависит от случайных неинициализированных данных).
Похоже, что заголовок gmpxx.h содержит что-то, из-за чего компилятор C++ в данном случае генерирует неверный код. Как нейтрализовать это нечто?
Возможный ответ: «Не используйте возвращаемый тип auto», однако я предпочитаю использовать его — это упрощает модификации.
Системная среда:





Документация по привязкам GMP C++ совершенно ясна: избегайте использования auto для выражений GMP. (На самом деле, даже обычные шаблоны могут вызвать проблемы).
Проблема в том, что GMP опирается на технику «шаблона выражения». Для mpz_class a, b это означает, что a + b не является самим mpz_class, а скорее объектом другого типа, который представляет сумму a и b без фактического ее вычисления. Такой объект обычно будет ссылаться на a и b по ссылке. Объект можно неявно преобразовать в mpz_class. Идея состоит в том, что этот метод позволяет специализировать функции в зависимости от функций, создавших их операнды. Например. вот игрушечный пример:
struct my_num { double num; };
template<typename... Ts>
struct my_num_sum { // expression template representing sums
std::tuple<Ts&...> summands;
operator my_num() {
// pretend that there is a more efficient method to add together multiple values of my_num at once than just repeatedly using +
return std::apply([](Ts&... summands) { return my_num((0 + ... + my_num(summands).num)); }, summands);
}
};
auto operator+(my_num &a, my_num &b) {
return my_num_sum<my_num, my_num>(std::tie(a, b));
}
// this specializes the addition operator to do something different (i.e. more efficient) when one of the operands is itself a sum
template<typename... Ts, typename U>
auto operator+(my_num_sum<Ts...> as, U &b) {
return my_num_sum<Ts..., U>(std::tuple_cat(as.summands, std::tie(b)));
}
В этом примере для my_num a, b, c выражение a + b + c — это не my_num, а my_num_sum<my_num, my_num, my_num>. Строка my_num d = a + b + c складывает a, b и c одновременно (за один вызов my_num_sum::operator my_num()), что может быть более эффективным, чем сложение a и b в промежуточный объект, а затем добавление к нему c. (Это неверно для double, но имеет смысл для более крупных типов, таких как векторы).
Вы видите, что теперь существует большая опасность: пользовательская функция может легко создавать висячие ссылки:
auto oops(my_num a, my_num b) { return a + b; }
// returns a my_num_sum containing dangling references to parameters!
Но проблема исчезает, если вы запрашиваете фактическое число, поскольку теперь используется неявное преобразование шаблона выражения в реальный тип данных.
my_num fine(my_num a, my_num b) { return a + b; }
Если вы действительно настаиваете на использовании возвращаемых типов auto, хорошо, но теперь вам все равно нужно добавить приведения к my_num в тех местах, где вы не хотите, чтобы шаблоны выражений утекли.
auto okay(my_num a, my_num b) { return my_num(a + b); }
Это именно те два исправления, которые предлагаются в документации GMP для вашей проблемы. Чтобы правильно определить test, напишите одно из следующих слов:
mpz_class test() { return mpz_class(2) * 2; }
auto test() { return mpz_class(mpz_class(2) * 2); }
Вам нужно где-то сказать mpz_class, потому что в противном случае не будет ничего, что могло бы вызвать неявное преобразование шаблона выражения в число.