Pull to refresh

Изменения в Visual C++

Reading time 12 min
Views 36K
Original author: msdn.microsoft.com
Когда вы захотите обновить версию Visual C++ компилятора (например, перейти с Visual Studio с 2013 на 2015), будет не лишним узнать, почему вы можете столкнуться с тем, что код, который прежде успешно компилировался и выполнялся, теперь будет вызывать ошибки компиляции и/или ошибки времени выполнения.
Эти проблемы могут быть вызваны многочисленными изменениями компилятора для соответствия стандарту С++, изменениями в сигнатурах функций или изменениями расположения объектов в памяти.

Для того, чтобы избежать ошибок времени выполнения (их, как известно, наиболее трудно найти), мы рекомендуем никогда не делать статическое связывание с двоичными файлами, полученными другой версией компилятора. Также при обновлении своей программы (EXE или DLL), убедитесь, что используемые библиотеки тоже собраны новой версией компилятора.
Если вы используете типы из CRT (C Runtime) или STL (Standard Template Library), не передавайте их между двоичными файлами (включая DLL), которые собраны разными версиями компилятора. Более подробно этот вопрос рассмотрен в Potential Errors Passing CRT Objects Across DLL Boundaries.
И в дальнейшем мы рекомендуем не писать код, который зависит от конкретного расположения объектов в памяти (если это не COM интерфейс или POD объект). Если у вас сейчас есть такой код, после обновления компилятора вам следует убедиться, что все работает как надо. Более подробно можно почитать здесь: Portability At ABI Boundaries (Modern C++).

Ниже в статье описываются изменения в компиляторе Visual C++ (который идет с Visual Studio 2015 Preview). В статье слова «новое поведение» и «сейчас» относятся именно к этой версии, а «старое поведение» и «раньше» — к Visual Studio 2013 (и более ранним версиям).

Краткое содержание:
— Изменения в компиляторе
— Изменения в C Runtime Library (CRT)
— Изменения в STL
— Изменения в MFC и ATL

Изменения в компиляторе


  • /Zc:forScope — Флаг компилятора /Zc:forScope- объявлен устаревшим (deprecated) и в будущем будет удален. Сейчас компилятор при использовании этого флага будет выкидывать предупреждение D9035.
    Данный флаг используется для использования нестандартного расширения С++ — использование переменных, объявленных в описании цикла for за пределами этого цикла. Этот флаг необходим только если установлен другой флаг — /Za (соответствовать стандарту), потому что без /Za, использование переменных из описания цикла по умолчанию разрешено. Если вам не нужно заботиться о кроссплатформенности (например, не предполагается собирать код другими компиляторами), вы можете выключить флаг /Za (установить свойству проекта «Disable Language Extensions» значение «No»). Если же вы заботитесь о кроссплатформенности и соответствия стандарту, значит такие участки кода нужно переписать, переместив объявление переменной выше цикла:
    // zc_forScope.cpp
    // compile with: /Zc:forScope- /Za
    // C2065 expected
    int main() {
       // Раскомментить следующую строку для исправления ошибки
       // int i;
       for (int i =0; i < 1; i++)
          ;
       i = 20;   // с флагом /Za переменная i здесь уже вне своего блока
    }
    

  • /Zg
    Флаг компилятора /Zg (создание прототипов функций) больше недоступен для использования (раньше у него был атрибут deprecated)

  • Теперь нельзя запустить юнит-тесты, использующие C++/CLI, из командной строки используя mstest.exe, взамен нужно использовать vtest.console.exe. Более подробно можно узнать об этом здесь: VSTest.Console.exe command-line options.

  • Ключевое слово mutable
    Теперь использование mutable, в соответствии со стандартом, допускается только применимо к именам членов класса, и не может быть применено к ссылкам, или именам, объявленным как const или static. Пример:
    struct S {
        mutable int &r;
    };
    

    Раньше это компилировалось, теперь компилятор выдаст ошибку С2071. Для исправления, нужно просто убрать mutable.

  • char_16_t и char_32_t
    Теперь нельзя использовать char_16_t и char_32_t в качестве псевдонимов пользовательских типов, потому что сейчас эти типы определяются как встроенные. Раньше было довольно распространенной практикой для авторов библиотек определять char_16_t и char_32_t в качестве псевдонимов для uint_16_t и uint_32_t, соответственно.
    #include <cstdint>
    
    typedef uint16_t char16_t; //C2628
    typedef uint32_t char32_t; //C2628
    
    int main(int argc, char* argv[])
    {
        uint16_t x = 1; uint32_t y = 2;
        char16_t a = x; 
        char32_t b = y; 
        return 0;
    }
    

    Для исправления нужно убрать объявление псевдонимов и переименовать любые другие идентификаторы конфликтующие со вновь введенными.

  • Нетиповые (non-type) параметры шаблонов
    Код, включающий в себя нетиповые параметры шаблонов сейчас правильно проверяется на совместимость типов. Например, следующий код раньше компилировался без ошибок:
    struct S1
    {
        void f(int);
        void f(int, int);
    };
    
    struct S2
    {
        template <class C, void (C::*Function)(int) const> void f() {}
    };
    
    void f()
    {
         S2 s2;
         s2.f<S1, &S1::f>();
    }
    

    Сейчас компилятор выдаст ошибку, так как тип параметра шаблона не соответствует типу переданного аргумента (тип параметра — указатель на константный метод, но f не является константной).
    error C2893: Failed to specialize function template 'void S2::f(void)'
    note: With the following template arguments:
    note: 'C=S1'
    note: 'Function=S1::f'

    Для избавления от этой ошибки убедитесь, что тип аргумента шаблона полностью соотвествует типу параметра.

  • __declspec(align)
    Компилятор больше не принимает __declspec(align) для функций. По правде говоря, он никогда и не принимал, но сейчас он будет выдавать ошибку С3323. Для избавления от нее просто уберите это выражение из объявления функции. Так как это и раньше не имело никакого эффекта, это не поменяет ничего в вашей программе.

  • Обработка исключений
    В обработке исключений произошло несколько изменений. Первое — объекты-исключения должны быть копируемыми и перемещаемыми. раньше подобный код компилировался, теперь будет ошибка:
    struct S {
    public:
        S();
    private:
        S(const S &);
    };
     
    int main()
    {
        throw S(); // error
    }
    

    Проблема здесь в том, что конструктор копирования объявлен приватным, таким образом, объект не может быть скопирован, что обычно требуется при обработке исключения. То же самое применимо в случае, когда конструктор объявлен explicit.
    struct S {
        S();
        explicit S(const S &);
    };
     
    int main()
    {
        throw S(); // error
    }
    

    Для избавления от этой проблемы убедитесь, что конструктор для объекта исключения объявлен в публичной зоне и explicit.
    При ловле исключения также требуется, чтобы объект был копируемым. Следующий код скомпилируется в ранних версиях Visual Studio, но сейчас будет ошибка:
    struct B {
    public:
        B();
    private:
        B(const B &);
    };
     
    struct D : public B {
    };
     
    int main()
    {
        try
        {
        }
        catch (D d) // error
        {
        }
    }
    

    Эту ошибку можно исправить, приняв исключение по ссылке:
    catch(D& d)
    {
    }
    

  • Строки и макросы
    Сейчас компилятор поддерживает определенные пользователем литералы (user defined literals — UDL). Как следствие этого, строки (точнее строковые литералы), за которыми без пробела поставлен макрос, интерпретируются как UDL, что может явиться причиной ошибки или неожиданного результата. Например, раньше это компилировалось без проблем:
    #define _x "there"
    char* func() {
        return "hello"_x;
    }
    int main()
    {
        char * p = func();
        return 0;
    }
    

    Компилятор интрепретировал возвращаемое функцией func значение как строку «hello», и макрос, который раскрывался в «there», а затем соединял два этих литерала в один. Сейчас компилятор интерпретирует это как UDL, но так как не может найти определение _х среди известных ему UDL, он выдает ошибку:
    error C3688: invalid literal suffix '_x'; literal operator or literal operator template 'operator ""_x' not found
    note: Did you forget a space between the string literal and the prefix of the following string literal?
    Для решения этой проблемы нужно поставить пробел между строкой и макросом.

  • Строки, расположенные рядом
    Так же, как и в предыдущем случае, из-за изменений в разборе строк, строящие рядом строковые литералы, которые не разделяет пробел, раньше интерпретировались как одна строка, а сейчас для корректной компиляции нужно добавить пробел:
    char * str = "abc""def";
    

    Просто добавьте пробел между строками:
    char * str = "abc" "def";
    

  • Размещающий new и delete
    Для соответствия стандарту С++14 было изменено поведение оператора delete. Детали можно прочитать в C++ Sized Deallocation. Была добавлена форма глобального оператора delete, которая принимает размер объекта. Важность этого изменения в том, что если в вашем коде есть оператор delete с такой же сигнатурой (соответствующая размещающему оператору new), вы получите ошибку компиляции (С2956, которая указывает на строку с использованием оператора new, так как именно в этом месте компилятор пытается определить подходящий оператор delete).
    Функция void operator delete(void*, size_t) была размещающим оператором delete, соответствующим функции размещающего оператора newvoid* operator new(size_t, size_t) из С++11. В новом стандарте С++14 этот delete стал обычной функцией деаллокации (то есть глобальным оператором delete). Стандарт требует следующего — если размещающий new ищет соответствующий delete и находит стандартную функцию, программа не скомпилируется.
    Например, предположим, ваш код определяет размещающий new и delete:
    void * operator new(std::size_t, std::size_t);
    void operator delete(void*, std::size_t) noexcept; 
    

    Проблема здесь в следующем: совпадение описания оператора размещающего delete и описания нового глобального оператора. Для того, чтобы избежать такого совпадения, можно использовать тип, отличный от size_t, при описании размера в размещающих операторах new и delete. Здесь нужно принять во внимание, что size_t зависит от компилятора, в Visual C++ это псевдоним для unsigned int. Хорошим решением здесь будет использование перечисления:
    enum class my_type : size_t {};
    

    Затем, нужно изменить объявления размещающих операторов new и delete так, чтобы вторым параметром вместо size_t они принимали my_type. Также нужно будет подправить передачу аргумента в вызов new (например, с использованием static_cast<my_type> совершить преобразование из целочисленного значения в перечисление) и изменить определение размещающих операторов, совершив преобразования из перечисления в целочисленный тип. Кроме перечисления можно использовать, например, класс, у которого будет поле типа size_t.
    В качестве альтернативного решения можно убрать размещающий new. Если ваш код использует размещающий new для реализации пула и передаваемый размер — это размер объекта для размещения или удаления, в этом случае новый оператор delete может вполне подойти для удаления этого пула.
    Если вы не хотите изменять поведение данных операторов в своем коде прямо сейчас, вы можете пока использовать флаг /Zc:sizedDealloc-, который вернет старое поведение. То есть не будет создано двух-аргументной функции удаления, и, таким образом, не будет никакого конфликта с определенной вами функцией удаления.

  • Поля объединений (union data members)
    Теперь ссылки не могут быть полем объединения (union). Раньше этот код компилировался, теперь — нет:
    union U1 {
        const int i;
    };
    union U2 {
       int &i;
    };
    union U3 {
        struct {int &i;};
    };
    

    Теперь возникают следующие ошибки:
    test.cpp(67): error C2625: 'U2::i': illegal union member; type 'int &' is reference type

    test.cpp(70): error C2625: 'U3::i': illegal union member; type 'int &' is reference type

    Для разрешения этой проблемы нужно изменить ссылку на указатель или просто обычное значение. Изменение типа на указатель потребует изменений в коде, который обращается к этому полю. Изменение типа на значение позволит менять значение в объединении, что повлияет на другие поля объединения. Также в этом случае может измениться размер объединения.

  • Безымянные объединения (anonymous unions)
    Безымянные объединения стали больше соответствовать стандарту. Раньше для безымянных объединений генерировался явный конструктор и деструктор. Сейчас они объявляются удаленными.
    struct S {
      S();
     };
     
     union {
      struct {
       S s;
      };
     } u; // C2280
    

    В Visual Studio 2015 Preview генерируются следующие ошибки:
    error C2280: '<unnamed-type-u>::<unnamed-type-u>(void)': attempting to reference a deleted function

    note: compiler has generated '<unnamed-type-u>::<unnamed-type-u>' here

    Для решения этой проблемы, нужно определить свой конструктор и/или деструктор
    struct S {
         // Определяем конструктор по умолчанию, добавляя пустое тело
        S() {} 
    };
    
    union {
        struct {
        S s;
        };
    } u;
    

  • Объединения с анонимными структурами
    Для того, чтобы обеспечить соответствие стандарту, было изменено поведение времени выполнения для членов анонимных структур в объединениях. Конструктор анонимных структур — членов объединений — больше не вызывается неявно при создании объединения. Также деструктор таких полей не вызывается неявно при выходе объединения из блока видимости. Пример:
    #include <stdio.h>
    struct S {
        S() { printf("Creating S\n"); }
        ~S(){ printf("Destroying S\n"); }
    };
    union U {
        struct {
        S s;
        };
        U() {}
        ~U(){}
    };
    
    void f()
    {
        U u;
        // Неявный вызов деструктора
    }
    
    int main()
    {
        f();
    
        char s[1024];
        printf("Press any key.\n");
        gets_s(s);
        return 0;
    }
    

    Раньше вызывался и конструктор и деструктор. Сейчас они не вызываются. Компилятор выдает предупреждения:
    warning C4587: 'U::s': behavior change: constructor is no longer implicitly called

    warning C4588: 'U::s': behavior change: destructor is no longer implicitly called

    Для восстановления старого поведения нужно дать имя анонимной структуре. Поведение времени выполнения неанонимных структур остается независимой от версии компилятора.
    #include <stdio.h>
    
    struct S {
        S() { printf("Creating S.\n"); }
        ~S() { printf("Destroying S\n"); }
    };
    union U {
        struct {
            S s;
        } namedStruct;
        U() {}
        ~U() {}
    };
    
    void f()
    {
        U u;
    }
    
    int main()
    {
        f();
    
        char s[1024];
        printf("Press any key.\n");
        gets_s(s);
        return 0;
    }
    

    В качестве альтернативного решения можно попытаться перенести код конструктора и деструктора структуры в новые методы и вызывать их из конструктора и деструктора объединения:
    #include <stdio.h>
    
    struct S {
        void Create() { printf("Creating S.\n"); }
        void Destroy() { printf("Destroying S\n"); }
    };
    union U {
        struct {
            S s;
        };
        U() { s.Create();  }
        ~U() { s.Destroy(); }
    };
    
    
    void f()
    {
        U u;
    }
    
    int main()
    {
        f();
    
        char s[1024];
        printf("Press any key.\n");
        gets_s(s);
        return 0;
    }
    

  • Разрешение шаблонов (template resolution)
    В разрешение имен для шаблонов также были внесены изменения. В С++, при рассмотрении кандидатов при разрешении имен, вполне возможен случай, когда одно или более имен могут быть неверными конкретизациями (invalid instantiation) шаблона. Эти неверные реализации обычно не вызывают ошибок компиляции (т.к. используется принцип, известный как SFINAE).

    Сейчас, если SFINAE требует от компилятора конкретизировать шаблон класса, любые ошибки во время выполнения этой конкретизации будут считаться ошибками компилятора. Раньше эти ошибки игнорировались. Например:
    #include <type_traits>
     
    template<typename T>
    struct S
    {
        S() = default;
        S(const S&);
        S(S&&);
    
        template<typename U, typename = typename std::enable_if<std::is_base_of<T, U>::value>::type>
        S(S<U>&&);
    };
     
    struct D;
     
    void f1()
    {
        S<D> s1;
        S<D> s2(s1);
    }
    
    struct B
    {
    };
    
    struct D : public B
    {
    };
    
    void f2()
    {
        S<D> s1;
        S<D> s2(s1);
    }
    

    Новый компилятор выдаст следующее сообщение об ошибке:
    type_traits(1110): error C2139: 'D': an undefined class is not allowed as an argument to compiler intrinsic type trait '__is_base_of'

    ..\t331.cpp(14): note: see declaration of 'D'

    ..\t331.cpp(10): note: see reference to class template instantiation 'std::is_base_of<T,U>' being compiled

    with
    [
        T=D,
        U=D
    ]
    

    Причина ошибки в том, что в месте первого вызова is_base_of класс D еще не определен.
    В этом случае исправление ошибки будет следующим: не нужно использовать такие проверки типов до определения классов, значит нужно перенести определение B и D d в начало файла. Если эти определения в заголовочных файлах, нужно проверить порядок включения, чтобы убедиться, что определения классов включаются раньше, чем используется проблемный шаблон.

  • Конструкторы копирования
    И в Visual Studio 2013 и в Visual Studio 2015 RC, компилятор создает конструктор копирования для класса, если в этом классе есть определенный пользователем конструктор перемещения, но нет определенного пользователем конструктора копирования. В Dev14 этот неявно генерируемый конструктор копирования помечается как "=delete".


Изменения в C Runtime Library (CRT)


Общие изменения


  • Рефакторинг бинарных файлов
    Библиотека CRT была разделена на два бинарных файла — на общая библиотеку CRT (Universal CRT — ucrtbase), которая содержит бОльшую часть стандартной функциональности, и библиотеку VC Runtime (vcruntime140), которая содержит зависимую от компилятора функциональность (например, обработка исключений и встроенные функции (intrinsics)). Если вы используете стандартные настройки проекта, тогда это изменение вас никак не затронет, так как компоновщик сам станет использовать эти новые библиотеки. Если же вы устанавливали свойство компоновщика проекта Ignore All Default Libraries в Yes, или вы использовали флаг линковщика /NODEFAULTLIB, тогда вам следует обновить свой список библиотек (свойство проекта Additional Dependencies), то есть включить в него новые библиотеки самому. А точнее — заменить старые CRT библиотеки (libcmt.lib, libcmtd.lib, msvcrt.lib, msvcrtd.lib) на новые эквиваленты. Для каждой из двух новых библиотек есть статическая (.lib) и динамическая (.dll) версии, а также релизная сборка (без суффикса) и отладочная (с суффиксом «d»). Более подробно здесь: CRT Library Features

 <locale.h> 


  • localeconv
    Функция localeconv, объявленная в locale.h теперь работает корректно в случаях, когда включена потоковая локаль (per-thread locale). Раньше эта функция возвращала Iconv глобальной, а не потоковой, локали.
    Если вы используете потоковую локаль, вам следует проверить, как новое поведение localeconv повлияет на вашу программу.

 <math.h> 


  • С++ перегрузка библиотечных функций
    Раньше в <math.h> перегружались некоторые, но не все, математические библиотечные функции. В была определена перегрузка остальных функций. Это вело к проблемам разрешения перегрузки, если включен только <math.h>. Теперь все перегрузки перенесены из <math.h> в .


    Приведение в соответствие стандарту чисел с плавающей точкой
    В математической библиотеке было сделано много изменений для приближения к соответствию стандарту IEEE-754 и Приложению F стандарта C11, основное внимание было уделено особым случаям (NaN и бесконечности). Например, при передаче в функцию библиотеки NaN раньше часто возникала ошибка, сейчас - не возникает. Более подробно узнать об этом можно здесь: IEEE 754 Standard, Annex F of the C11 Standard.
    Эти изменения не вызовут ошибок времени компиляции, однако программа может начать вести себя по другому - более соответственно стандартам.


    FLT_ROUNDS
    Раньше этот макрос разворачивался в константное выражение, что было неправильно, так как способ округления числа мог измениться во время выполнения программы (например, с помощью fesetround). Сейчас FLT_ROUNDS это учитывает.


    <new> and <new.h>


    • new и delete
      В предыдущей версии библиотеки зависимые от реализации операторы new и delete экспортировались из динамической библиотеки (например msvcr120.dll). Эти операторы сейчас всегда линкуются статически с вашими двоичными файлами, даже если используются динамические библиотеки.
      Это не очень значительное изменение для нативного или смешанного кода (/clr), но если ваш код скомпилирован с флагом /clr:pure то после обновления код может перестать компилироваться. В этом случае нужно убедиться, что во всех нужных местах подключены заголовочные файлы или <new.h>. Еще нужно учесть следующее: флаг /clr:pure объявлен устаревшим и будет удален в одном из следующих релизов.

Tags:
Hubs:
+31
Comments 11
Comments Comments 11

Articles