Поделиться через


Критические изменения в Visual C++

При обновлении до новой версии компилятора C Visual C++ могут возникать ошибки компиляции и ошибки во время выполнения кода, который ранее правильно компилировался и выполнялся.Изменения в новой версии, вызывающие такие проблемы, именуются критическими изменениями и, как правило, связаны с изменениями стандарта языка C++, сигнатур функций или структуры объектов в памяти.

Несмотря на то, что в Visual C++ структура объектов POD (обычные старые данные) и интерфейсы COM идентичны в любой версии, другие структуры объектов (например, в сценариях, включающих наследование или создание экземпляров шаблонов) могут изменяться.

Чтобы избежать ошибок во время выполнения, которые сложно выявить и диагностировать, рекомендуется не создавать статических ссылок на двоичные файлы, скомпилированные при помощи других версий компилятора.Кроме того, при обновлении проекта EXE или DLL обязательно обновите библиотеки, на которые он ссылается.При использовании типов CRT (среда выполнения языка C) или STL (библиотека стандартных шаблонов) не передавайте их между двоичными файлами (в том числе файлами DLL), скомпилированными при помощи разных версий компилятора.Для получения дополнительной информации см. Потенциальные ошибки при передаче объектов CRT через границы DLL.

Также рекомендуется не создавать код, зависящий от конкретной структуры, для объекта, который не является интерфейсом COM или объектом POD.Если вы написали подобный код, после обновления обязательно убедитесь в его работоспособности.Для получения дополнительной информации см. Переносимость на границах API (современный C++).

Далее в данной статье приводится информация по конкретным критическим изменениям в Visual C++ в Visual Studio 2013.

Компилятор Visual C++

  • Окончательное ключевое слово теперь генерирует ошибку "неразрешенный символ", хотя ранее оно компилировалось:

    struct S1 {
        virtual void f() = 0;
    };
    
    struct S2 final : public S1 {
        virtual void f();
    };
    
    int main(S2 *p)
    {
        p->f();
    }
    

    В более ранних версиях ошибка не выдавалась, поскольку вызов был виртуальным вызовом; тем не менее во время выполнения программа давала сбой.Теперь выдается ошибка компоновщика, поскольку известно, что класс является конечным.В этом примере для устранения ошибки нужно выполнить по компоновку объекту, который содержит определение S2::f.

  • При использовании дружественных функций в пространствах имен необходимо повторно объявить дружественную функцию, прежде чем сослаться на нее; в противном случае возникнет ошибка, поскольку компилятор теперь соответствует стандарту ISO C++.Например, больше не компилируется следующий код:

    namespace NS {
        class C {
            void func(int);
            friend void func(C* const) {}
        };
    
        void C::func(int) { 
            NS::func(this);  // error
        }
    }
    

    Для исправления этого кода объявите дружественную функцию:

    namespace NS {
        class C {
            void func(int);
            friend void func(C* const) {}
        };
    
        void func(C* const);  // conforming fix
    
        void C::func(int) { 
            NS::func(this); 
        }
    }
    
  • Стандарт C++ не допускает явной специализации в классе.В некоторых случаях Visual C++ в некоторых случаях это допускает, однако в таких случаях, как в следующем примере, возникает ошибка, потому что компилятор не считает вторую функцию специализацией первой.

    template <int N>
    class S { 
    public: 
        template  void f(T& val); 
        template <> void f(char val); 
    }; 
    
    template class S<1>; 
    

    Для исправления этого кода измените вторую функцию:

    template <> void f(char& val);
    
  • Visual C++ больше не пытается устранить неоднозначность между двумя функциями в следующем примере, и теперь выдает ошибку:

    template<typename T> void Func(T* t = nullptr); 
    template<typename T> void Func(...); 
    
    int main() { 
        Func<int>(); // error
    } 
    

    Для исправления этого кода уточните вызов:

    template<typename T> void Func(T* t = nullptr); 
    template<typename T> void Func(...); 
    
    int main() { 
        Func<int>(nullptr); // ok
    } 
    
  • До того, как компилятор стал совместимым с ISO C++11, следующий код скомпилировался бы, и переменная x разрешилась бы в тип int:

    auto x = {0}; 
    
    int y = x; 
    

    Теперь в этом коде x разрешается в тип std::initializer_list<int>, что вызывает ошибку на следующей строке, где производится попытка присвоить x типу int.(По умолчанию преобразования нет.) Для исправления этого кода используйте int вместо auto:

    int x = {0}; 
    
    int y = x; 
    
  • Агрегатная инициализация больше не допускается, если тип значения справа не соответствует типу инициализируемого значения слева; в этом случае возникает ошибка, поскольку стандарт ISO C++11 требует, чтобы унифицированная инициализация работала без сужающих преобразований.Ранее, если сужающее преобразование было доступно, вместо ошибки выдавалось предупреждение C4242.

    int i = 0;
    char c = {i}; // error
    

    Для исправления этого кода добавьте явное сужающее преобразование:

    int i = 0;
    char c = {static_cast<char>(i)};
    
  • Следующая инициализация больше не является допустимой.

    void *p = {{0}};
    

    Для исправления этого кода используйте одну из следующих форм записи:

    void *p = 0; 
    // or 
    void *p = {0};
    
  • Изменился поиск имен. Следующий код разрешается по-разному в Visual C++ в Visual Studio 2012 и Visual C++ в Visual Studio 2013:

    enum class E1 {a};
    enum class E2 {b};
    
    int main()
    {
        typedef E2 E1;
        E1::b;
    }
    

    В Visual C++ в Visual Studio 2012 E1 в выражении E1::b разрешается в ::E1 в глобальной области видимости.В Visual C++ в Visual Studio 2013 E1 в выражении E1::b разрешается в определение typedef E2 в main() и имеет тип ::E2.

  • Макет объектов изменен. В x64 макет объектов класса может отличаться от аналогичного макета в предыдущих выпусках.Если он обладает виртуальной функцией, но не обладает базовым классом, обладающим виртуальной функцией, то модель объекта компилятора вставляет указатель в таблицу виртуальной функции после макета данных-членов.Это означает, что макет не во всех случаях будет оптимальным.В предыдущих выпусках технология оптимизации x64 предпринимала попытку улучшения макета в соответствии с требованиями пользователя, но поскольку в случае работы со сложными кодами она не выполняла свои функции должным образом, из Visual C++ в Visual Studio 2013 данная технология была удалена.Например, рассмотрим следующий код.

    __declspec(align(16)) struct S1 {
    };
    
    struct S2 {
        virtual ~S2();
        void *p;
        S1 s;
    };
    

    В Visual C++ в Visual Studio 2013 результат sizeof(S2) в x64 — 48, но в предыдущих выпусках он равен 32.Чтобы получить значение 32 в Visual C++ в Visual Studio 2013 для x64, добавьте фиктивный базовый класс, обладающий виртуальной функцией:

    __declspec(align(16)) struct S1 {
    };
    
    struct dummy { 
        virtual ~dummy() {} 
    };
    struct S2 : public dummy {
        virtual ~S2();
        void *p;
        S1 s;
    };
    

    Чтобы найти сегменты кода, которые предыдущие версии попытались бы оптимизировать, используйте компилятор из данного выпуска в сочетании с параметром компилятора /W3 и включите предупреждение 4370.Например:

    #pragma warning(default:4370)
    
    __declspec(align(16)) struct S1 {
    };
    
    struct S2 {
        virtual ~S2();
        void *p;
        S1 s;
    };
    

    Для компиляторов Visual C++ C до версии Visual C++ в Visual Studio 2013 этот код выводит следующее сообщение:

    warning C4370: 'S2' : layout of class has changed from a previous version of the compiler due to better packing
    

    Проблема неоптимального макета характерна для компилятора x86 во всех версиях Visual C++.Например, если такой код компилируется для x86:

    struct S {
        virtual ~S();
        int i;
        double d;
    };
    

    Результат выражения sizeof(S) — 24.Однако это значение можно уменьшить до 16 при помощи вышеуказанного решения данной проблемы для x64:

    struct dummy { 
        virtual ~dummy() {} 
    };
    
    struct S : public dummy {
        virtual ~S();
        int i;
        double d;
    };
    

Библиотеки Visual C++

Библиотека стандартных шаблонов

Для активации новых способов оптимизации и проверки результатов отладки стандартная библиотека C++, реализованная в Visual Studio, намеренно ограничивает совместимость двоичных данных из одной версии в следующей версии.Поэтому при использовании стандартной библиотеки C++ файлы объектов и статические библиотеки, скомпилированные с помощью разных версий, нельзя одновременно добавить в один двоичный файл (EXE или DLL), а объекты стандартной библиотеки C++ нельзя передать из одного двоичного файла в другой, если эти файлы скомпилированы при помощи разных версий.В этом случае возникает ошибка компоновщика, связанная с несоответствиями _MSC_VER.(_MSC_VER представляет собой макрос, содержащий основной номер версии компилятора — например, 1800 для Visual C++ в Visual Studio 2013.) Эта проверка не может обнаружить смешение DLL и смешение, в котором участвует Visual C++ 2008 или более ранних версий.

Visual C++ в Visual Studio 2013 обнаруживает несоответствия в _ITERATOR_DEBUG_LEVEL (функция, реализованная в Visual C++ 2010), а также несоответствия RuntimeLibrary.Это происходит при смешении параметров компилятора /MT (статический выпуск), /MTd (статическая отладка), /MD (динамический выпуск) и /MDd (динамическая отладка).Дополнительные сведения см. в разделе Критические изменения в Visual C++ 2012.

  • Если код признает имитируемые шаблоны псевдонима предыдущего выпуска, необходимо внести в него изменения.Например, вместо allocator_traits<A>::rebind_alloc<U>::other теперь необходимо писать allocator_traits<A>::rebind_alloc<U>.Несмотря на то, что ratio_add<R1, R2>::type больше не требуется, и теперь мы рекомендуем использовать ratio_add<R1, R2>, первый вариант по-прежнему будет компилироваться, поскольку отношение ratio<N, D> обязательно должно иметь typedef "тип" для уменьшенного отношения (то есть тот же тип, если оно уже уменьшено).

  • При вызове #include <algorithm> или std::min() необходимо использовать std::max().

  • Если в существующем коде используются имитируемые ограниченные перечисления предыдущего выпуска (традиционные неограниченные перечисления с оболочкой из пространства имен), в него необходимо внести изменения.Например, если вы ссылались на тип std::future_status::future_status, теперь необходима запись std::future_status.Однако на большую часть кода это не влияет, например std::future_status::ready по-прежнему компилируется.

  • explicit operator bool() строже, чем operator unspecified-bool-type().explicit operator bool() разрешает явные преобразования в bool — например, при shared_ptr<X> sp и static_cast<bool>(sp), и bool b(sp) являются допустимыми — и логически проверяемые "контекстные преобразования" в bool — например, if (sp), !sp, sp && whatever.Однако explicit operator bool() запрещает неявные преобразования в bool, поэтому нельзя написать bool b = sp; а при возвращаемом типе bool нельзя написать return sp.

  • Теперь, когда реализованы настоящие шаблоны с переменным числом аргументов, определение препроцессора _VARIADIC_MAX и связанные с ним макросы не имеют силы. Если теперь определить _VARIADIC_MAX, это определение просто игнорируется. Если вы признали механизмы макросов, предназначенные для поддержки имитируемых шаблонов с переменным числом аргументов каким-либо другим способом, необходимо внести изменения в код.

  • В дополнение к обычным ключевым словам в заголовках STL теперь запрещено использование макросов, связанных с контекстом ключевых слов override и final.

  • reference_wrapper/ref()/cref() теперь запрещает привязку к временным объектам.

  • <random> теперь строго контролирует соблюдение своих предусловий времени компиляции.

  • Различные характеристики типов STL имеют предусловие "T должен быть полным типом".Несмотря на то, что теперь компилятор контролирует это предусловие более строго, он не может обеспечивать его выполнение во всех ситуациях.(Поскольку нарушение предусловия STL инициирует неопределенное поведение, стандарт не гарантирует принудительное применение этого предусловия).

  • STL не поддерживает /clr:oldSyntax.

  • Спецификация C++11 для common_type<> имела неожиданные и нежелательные последствия; в частности, в соответствии с ней common_type<int, int>::type возвращает int&&.Поэтому Visual C++ реализует предлагаемое разрешение задачи 2141 рабочей группы библиотек, которое заставляет common_type<int, int>::type возвращать int.

    В качестве побочного эффекта этого изменения вариант с идентификатором больше не работает (common_type<T> не всегда дает тип T).Это соответствует предлагаемому решению, однако нарушает работу кода, в котором предполагается ранее существовавшее поведение.

    Если требуется характеристика типа идентификатора, не используйте нестандартную структуру std::identity, определенную в <type_traits>, поскольку она не будет работать для <void>.Вместо этого реализуйте собственную характеристику типа идентификатора в соответствии со своими потребностями.Ниже приведен пример:

    template <typename T> struct Identity {
        typedef T type;
    };
    

MFC и ATL

  • Библиотека MFC MBCS больше не входит в состав в Visual Studio, поскольку из-за популярности Юникода многобайтовая кодировка теперь используется значительно реже.Это изменение также обеспечивает более точное соответствие MFC самому Windows SDK, поскольку многие из новых элементов управления и сообщений поддерживают только Юникод.Однако если необходимо продолжить использование библиотеки MFC с многобайтовой кодировкой, ее можно загрузить из Центра загрузки MSDN.Распространяемый пакет Visual C++ по-прежнему содержит эту библиотеку.

  • Изменился порядок доступа к ленте MFC.  Вместо одноуровневой архитектуры теперь используется иерархическая архитектура. Старое поведение по-прежнему можно использовать, вызывая CRibbonBar::EnableSingleLevelAccessibilityMode().

  • Удален метод CDatabase::GetConnect. Для большей безопасности строка подключения теперь хранится в зашифрованном виде и расшифровывается только по мере необходимости; ее невозможно возвратить в виде обычного текста.  Получить строку можно с помощью метода CDatabase::Dump.

  • Изменилась сигнатура CWnd::OnPowerBroadcast. Сигнатура этого обработчика сообщений теперь принимает LPARAM в качестве второго параметра.

  • Изменились сигнатуры для поддержки обработчиков сообщений. Списки параметров следующих функций были изменены для использования вновь добавленных обработчиков сообщений ON_WM_*:

    • Сигнатура функции CWnd::OnDisplayChange изменилась с (UINT, int, int) на (WPARAM, LPARAM), чтобы можно было использовать в схеме сообщений новый макрос ON_WM_DISPLAYCHANGE.

    • Сигнатура функции CFrameWnd::OnDDEInitiate изменилась с (CWnd*, UINT, UNIT) на (WPARAM, LPARAM), чтобы можно было использовать в схеме сообщений новый макрос ON_WM_DDE_INITIATE.

    • Сигнатура функции CFrameWnd::OnDDEExecute изменилась с (CWnd*, HANDLE) на (WPARAM, LPARAM), чтобы можно было использовать в схеме сообщений новый макрос ON_WM_DDE_EXECUTE.

    • В сигнатуре функции CFrameWnd::OnDDETerminate вместо (CWnd*) теперь используется параметр (WPARAM, LPARAM), чтобы можно было использовать в схеме сообщений новый макрос ON_WM_DDE_TERMINATE.

    • У функции CMFCMaskedEdit::OnCut вместо (WPARAM, LPARAM) теперь нет параметров, чтобы можно было использовать в схеме сообщений новый макрос ON_WM_CUT.

    • У функции CMFCMaskedEdit::OnClear вместо (WPARAM, LPARAM) теперь нет параметров, чтобы можно было использовать в схеме сообщений новый макрос ON_WM_CLEAR.

    • У функции CMFCMaskedEdit::OnPaste вместо (WPARAM, LPARAM) теперь нет параметров, чтобы можно было использовать в схеме сообщений новый макрос ON_WM_PASTE.

  • #ifdef-директивы в файлах заголовков MFC удалены. Многочисленные директивы препроцессора #ifdef в файлах заголовков MFC, относящиеся к старым неподдерживаемым версиям Windows (WINVER < 0x0501), удалены.

  • Удалена библиотека ATL DLL (atl120.dll). ATL теперь предоставляется в виде заголовков и статической библиотеки (atls.lib).

  • Удалены Atlsd.lib, atlsn.lib и atlsnd.lib. Библиотека Atls.lib больше не имеет зависимостей от кодировки или кода, относящегося исключительно к отладке или выпуску.Поскольку она работает одинаково для Юникода/ANSI и отладки/выпуска, достаточно одной версии библиотеки.

  • Средство трассировки ATL/MFC удалено вместе с DLL-библиотекой ATL, и механизм трассировки упрощен.Конструктор CTraceCategory теперь принимает один параметр (имя категории), а макросы TRACE вызывают функции отчетов отладки CRT.

См. также

Другие ресурсы

Начало работы с Visual C++ в Visual Studio 2013