- PVSM.RU - https://www.pvsm.ru -
В этой статье будет рассмотрен ряд багов в реализации стандарта С++11, которые имелись в Visual Studio 2012 и были исправлены в Visual Studio 2013. Таким образом мы теперь можем использовать С++11 так, как в теории предполагается его использовать. Если для обхода багов VS2012 вам пришлось понаписывать «костылей» — теперь их можно убрать.
К сожалению, не все баги были исправлены, кое-что мигрировало из VS2012 в VS2013, а также появились новые баги. Под катом вы найдёте детальный разбор текущего состояния дел.
Visual Studio 2013 поддерживает variadic templates в полном объёме, вещи вроде std::function или make_shared больше не имеют лимита на количество аргументов, которые они могут принять. В Visual Studio 2012 этот лимит существовал и был равен 5 (пяти).
Если вы использовали auto для объявления переменной типа, определяемого выражением __declspec(align(…)), align обрабатывался неверно в VS2012, что приводило к неправильному расположению данных в памяти и случайным крешам.
Несмотря на то, что тип, определяемый через decltype предположительно должно быть возможно использовать везде, где можно использовать обычные типы, VS2012 не позволяла написать, к примеру, вот такой код:
vector<int> a;
decltype(a)::iterator iter = a.end(); // ошибка в VS2012
Некоторый корректный согласно стандарту С++11 код не компилировался в VS2012 из-за неверной интерпретации declval.
Предположим, вы ходите объявить шаблон is_comparable:
template<typename, typename = true_type>
struct is_comparable : public false_type
{};
template<typenameT>
struct is_comparable<T,
typename is_convertible<decltype(declval<T>() > declval<T>()),
bool>::type> : public true_type
{};
Это не сработает в VS2012, поскольку declval не поймёт, что такое T.
Если вы использовали лямбда-функции для определение того, что должно случиться при удалении умного указателя, вы не могли использовать этот указатель в контексте преобразование его к типу bool:
auto stream_deleter = [](ofstream* os) { os->close(); };
unique_ptr<ofstream, decltype(stream_deleter)> p_log(&log_file, stream_deleter);
if (!p_log) // compile error
cout << "Couldn't open file" << endl;
Порядок операций в методе reset не соответствовал порядку, описываему стандартом. Это могло приводить к двойному удалению объекта. Пример:
class SelfReferential
{
unique_ptr<SelfReferential>& _p_self;
public:
SelfReferential(unique_ptr<SelfReferential>& p) : _p_self(p)
{}
~SelfReferential()
{
_p_self.reset();
}
};
unique_ptr<SelfReferential> p;
p = unique_ptr<SelfReferential>(new SelfReferential(p));
p.reset(); // двойное удаление через ~SelfReferential
Вызов метода reset запускает деструктор SelfReferential, который снова вызвает reset. Двойное удаление происходит потому, что метод reset сбрасывает указатель на подконтрольный объект после его удаления, а не до него.
Вы не могли создать shared_ptr для класса с protected-деструктором, инициализировав его nullptr:
class Interface
{
public:
virtual void do_stuff() = 0;
protected:
~Interface() {}
};
class Implementation : public Interface
{
public:
void do_stuff() override
{
// ...
}
};
shared_ptr<Interface> ptr1 = make_shared<Implementation>(); // OK
shared_ptr<Interface> ptr2 = nullptr; // ошибка
В Visual Studio 2013 этот код компилируется, как и предполагается стандартом.
is_function возвращает неверный результат, если переданная ей функция содержит слишком много аргументов:
typedef void f(int, bool, int*, int[], int, int, int, int, int, int, int);
is_function<f>::value; // false, хотя должно быть true
Также результат ошибочен для функций с calling convention, отличным от дефолтного
Аналогично, is_member_function_pointer не удаётся верно вернуть результат для методов с явно заданным calling convention
is_member_pointer, наоборот, не верно работает с __cdecl-методом:
typedef void (__cdecl A::*ccall_proc)(int, long, double);
is_member_pointer<ccall_proc>::value; // false, но должно быть true
is_object был определен через is_function, таким образом вышеуказанная ошибка с большим количеством аргументом функции распространяется и на него, приводя к неверному определению объекта.
is_scalar<nullptr_t> ошибочно возвращал false в VS2012 – стандарт определяет nullptr_t как скалярный тип.
is_pod ошибочно возвращал true в VS2012, хотя void не является POD-типом [1]
is_constructible вёл себя неверно со ссылочными типами, возвращая false для вещей типа:
is_constructible<const string&, string>::value;
is_constructible<const string&, string&&>::value;
alignment_of в VS2012 генерирует generatee a ложные предупреждения о недоступном деструкторе, если вы используете его на типе с приватным деструктором.
Кроме того, aligned_union работал неверно в VS2012:
typedef aligned_union<16, string>::type StorageType;
sizeof(string); // 24
sizeof(StorageType); // 16, но должно быть 24 или больше
aligned_union должен иметь статический член alignment_value, содержащий значения выравнивания для шаблонных аргументов T1, …, Tn. Это, однако, не было реализовано в VS2012.
Вместо ошибки компиляции, как это предполагается по стандарту, common_type возвращал void в VS2012:
common_type<int, string>::type; // void
common_type также возвращает void ошибочно для пользовательских типов, когда преобразование *is* возможно:
struct A {};
struct AWrapper {
AWrapper() {}
AWrapper(const A&) {}
};
common_type<A, AWrapper>::type; // void
Если вы решите использовать move-only аргумент с этим шаблоном в VS2012, у вас будут неприятности:
result_of<Copyable(MoveOnly&&)>::type; // ошибка компиляции
Стандартом определяется две версии этого алгоритма:
pair<Iter, Iter> minmax_element(Iter first, Iter last)
pair<Iter, Iter> minmax_element(Iter first, Iter last, Compare comp)
Они должны возвращать (first, last), где first указывает на наименьший элемент, а last — на наибольший, или make_pair(first, first) — если диапазон пуст. В VS2012, однако, вместо этого возвращалось make_pair(min_element(first, last), max_element(first, last)).
Все move-конструкторы контейнеров ошибочно требовали от типа элемента наличия move-конструктора.
struct A
{
A() {}
private:
A(A&&);
A(const A&);
};
deque<A> source;
deque<A> target(move(source)); // ошибка компиляции
Аналогично, операторы доступа к элементам map и unordered_map требовали обязательного наличия move-конструкторов:
map<string, A> m;
A& elem = m["abc"]; // ошибка компиляции
Ещё один баг в VS2012 был в реализации future и shared_future для ссылочных типов и void. Этот баг позволял следующему коду скомпилироваться (что явно ошибочно, поскольку future — это move-only тип):
future<int&> f_ref;
shared_future<int&> sf_ref(f_ref); // компилируется, хотя и не должно
future<void> f_void;
shared_future<void> sf_void(f_void); // компилируется, хотя и не должно
Баг, который мог приводить к утечкам памяти при завершении программы. Это случалось потому, что поток создавал, но никогда не уничтожал объект at_thread_exit_mutex, а так же некоторые внутренние структуры данных.
Из-за бага в Visual Studio 2012, функции wait_for и wait_until таких future-объетов возвращали future_status::deferred вместо of future_status::timeout or future_status::ready, делая эти методы бесполезными.
Баг несоответствия кода ошибки и её описания, из-за чего, например, получив исключение «broken promise», сообщение будет содержать текст «future already retrieved». Правильным был только код ошибки.
Вы получали сообщение об ошибки при попытке использования atomic-шаблона для типа без конструктора по-умолчания, хотя это и не верно.
В VS2012 atomic-операции иногда перебарщивали с проверками целостности (делали их там, где это не обязательно). Хотя это и не нарушает стандарта, код работал медленнее, чем мог бы. VS2013 имеет совершенно новую реализацию atomic-операций, которая работает значительно быстрее.
В дебаг-режиме mersenne_twister_engine генерировал ошибочный assert, если вы пытались инициализировать его нулём.
Потоковый оператор для subtract_with_carry_engine содержал ошибку, приводящую к неопределенному поведению.
independent_bits_engine и shuffle_order_engine не инициализировали внутренние члены в своих конструкторах перемещения, что иногда приводило к бесконечным циклам.
В библиотеке было найдено несколько багов:
Вы не могли написать следующий код в VS2012
ratio_add<ratio<1, 2>, ratio<1, 3>>::num;
ratio_add<ratio<1, 2>, ratio<1, 3>>::den;
Вместо этого вы были вынуждены обращаться к числитею и знаменателю через их тип:
ratio_add<ratio<1, 2>, ratio<1, 3>>::type::den;
ratio_add<ratio<1, 2>, ratio<1, 3>>::type::num;
Ещё одна ошибка была в реализации сравнения.
cout << "2/60 < -1/3: " << ratio_less<r2_60, r1_3>::value << endl; // false
cout << "2/60 < 1/-3: " << ratio_less<r2_60, ratio<1, -3>>::value << endl; // true но должно быть false
Таким образом в VS2012 вы должны были быть уверенными, что знаменатель, переданный в шаблон — всегда положительное число.
Ещё один баг был в том, что ratio_equal правильно определял неравенство, но не всегда правильно определял равенство:
ratio_equal<ratio<1, 4>, ratio<4, 16>>::value; // false но должно быть true
И вот ещё одни баг. Когда у вас есть ratio<N, D>, если D — ноль или число, превышающее intmax_t — ваша программ однозначно невалидна. Visual Studio 2012, однако, не определяла такие ошибки:
typedef ratio<1, 0> r_error;
cout << r_error::den << endl; // не должно компилироваться, но компилируется
typedef ratio<INTMAX_MIN, 1> r_error2;
cout << r_error2::num << endl; // не должно компилироваться, но компилируется
В реализации Visual Studio, static_assert утверждения, которые должны бы были срабатывать в этих ситуациях, размещены в конструкторе ratio. Но конструктор срабатывает только при создании объекта класса, а в примерах выше этого не происходит.
Аналогично, некоторый код компилируется, хотя и не должен:
// компилируется, хотя и не должен - с предупреждением о переполнении
ratio_multiply<ratio<1, INTMAX_MAX>, ratio<1, 2>>::type;
tuple_element<I, array<T, N>> должен проверят, что I < N и не компилироваться, если это не так. Этого не происходило до VS2013.
В некоторых случаях преобразование могло давать неверный результат в VS2012, поскольку объект не был пуст, когда по идее должен быть им:
// JetPlane наследуется от Plane
function<bool(JetPlane*)> plane_ready_func = function<bool(Plane*)>();
if (plane_ready_func) // должно быть false, но нет
{
plane_ready_func(nullptr); // вызывается и бросает bad_function_call
}
Visual Studio 2012 не запрещает присваивание для rvalues, как это определено стандартом:
struct Dummy
{
int _x;
};
Dummy get_dummy()
{
Dummy d = { 10 };
return d;
}
get_dummy()._x = 20; // компилируется, хотя и не должно
Функция корректно рассчитывает возвращаемый адресс, но неверно обновляет два последних параметра:
void* p = (void*)0x1;
// пытаемся выровнять 200 байт по границе в 32 байта
// при общем доступном объёме в 230 байт
size_t space = 230;
void* res = align(32, 200, (void*&)p, space);
// res равен null поскольку 31 байт необходим для выравнивания,
// но в наличии только 30
space = 256; // увеличиваем общий объём до 256
res = align(32, 200, (void*&)p, space);
// res теперь 0x20 (выровнян до границы в 32)
// p равен 0xE8 (200 + 32) но должен быть 0x20
// space равен 25 но должен біть 225
time_put не генерирует вывода когда инициализирован wchar_t.
Полный список изменений в VS2013 (не только в C++11) вы можете прочесть вот в этом посте [2], написанном Stephan Lavavej.
В дополнение к реализации новых возможностей С++11, в Visual Studio 2013 исправлено множество баков в существующей функциональности компилятора и библиотек, от некорректных ошибок компилятора до утечек памяти и низкой производительности. Это однозначно положительная динамика.
К сожалению, VS2013 всё ещё содержит некоторое количество багов, доставшееся по наследству от VS2012 и добавляет некоторые новые. Обо всём этом я сейчас пишу книгу, она ещё не закончена, но кое-что вы можете прочесть уже сейчас [3].
Автор: tangro
Источник [4]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/news/55880
Ссылки в тексте:
[1] POD-типом: http://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D1%81%D1%82%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D1%83%D0%BA%D1%82%D1%83%D1%80%D0%B0_%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D1%85#.D0.92_C.2B.2B11
[2] этом посте: http://blogs.msdn.com/b/vcblog/archive/2013/06/28/c-11-14-stl-features-fixes-and-breaking-changes-in-vs-2013.aspx
[3] уже сейчас: http://cpprocks.com/
[4] Источник: http://habrahabr.ru/post/213863/
Нажмите здесь для печати.