Visual Studio 2019 中的 C++ 一致性改善、行為變更和錯誤修正

Visual Studio 中的 Microsoft C/C++ 會在每個版本中進行一致性改善和 Bug 修正。 本文依主要版次和版本的順序列出列出改善。 若要直接跳至特定版本的變更,請使用下列文章中的清單。

本檔列出 Visual Studio 2019 中的變更。 如需 Visual Studio 2022 中變更的指南,請參閱 Visual Studio 2022 中的 C++ 一致性改進。 如需 Visual Studio 2017 中的變更,請參閱 Visual Studio 2017 中的 C++ 一致性改善。 如需先前一致性改善的完整清單,請參閱 Visual C++ 新功能 2003 到 2015

Visual Studio 2019 RTW 中的一致性改善 (16.0 版)

Visual Studio 2019 RTW 包含下列 Microsoft C++ 編譯程式中的一致性改進、錯誤修正和行為變更。

注意

C++20 功能只能在 Visual Studio 2019 的模式中使用 /std:c++latest ,直到 C++20 實作視為完成為止。 Visual Studio 2019 16.11 版引進編譯 /std:c++20 程式模式。 在本文中,原本需要 /std:c++latest 模式的功能現在可在 /std:c++20 最新版Visual Studio的模式或更新版本中運作。 我們已更新檔以提及 /std:c++20,即使第一次發行功能時無法使用此選項也一樣。

改善的範本和錯誤偵測模組支援

模組現已正式使用 C++20 標準。 Visual Studio 2017 15.9 版已新增改善的支援。 如需詳細資訊,請參閱 Better template support and error detection in C++ Modules with MSVC 2017 version 15.9 (C++ 模組與 MSVC 2017 15.9 版的更佳範本支援和錯誤偵測)。

已修改彙總類型的規格

C++20 已變更彙總類型的規格 (請參閱 Prohibit aggregates with user-declared constructors (禁止使用使用者宣告的建構函式彙總))。 在 Visual Studio 2019 中, /std:c++latest 在 (或 /std:c++20 Visual Studio 2019 16.11 版和更新版本中),具有任何使用者宣告建構函式的類別(例如,包括宣告的 = default 建構函式或 = delete)不是匯總。 過去,只有使用者所提供建構函式才可取消讓類別成為彙總的資格。 這項變更會對如何初始化這類類型施加更多限制。

下列程式代碼會在 Visual Studio 2017 中編譯,但會在 或 /std:c++latest/std:c++20引發 Visual Studio 2019 中的 C2280 和 C2440 錯誤:

struct A
{
    A() = delete; // user-declared ctor
};

struct B
{
    B() = default; // user-declared ctor
    int i = 0;
};

A a{}; // ill-formed in C++20, previously well-formed
B b = { 1 }; // ill-formed in C++20, previously well-formed

部分支援 operator <=>

P0515R3 C++20 引入 <=> 三向比較運算子,也稱為「太空船運算子」。 Visual Studio 2019 16.0 版在模式中 /std:c++latest 引進部分支援 運算符,方法是引發目前不允許語法的錯誤。 例如,下列程序代碼會在 Visual Studio 2017 中編譯,而不會在 或 /std:c++latest底下/std:c++20引發 Visual Studio 2019 中的多個錯誤:

struct S
{
    bool operator<=(const S&) const { return true; }
};

template <bool (S::*)(const S&) const>
struct U { };

int main(int argc, char** argv)
{
    U<&S::operator<=> u; // In Visual Studio 2019 raises C2039, 2065, 2146.
}

為避免發生錯誤,請在有問題程式行的最後一個角括弧前插入空格:U<&S::operator<= > u;

參考具有不相符 cv 限定詞的類型

注意

這項變更只會影響 Visual Studio 2019 16.0 到 16.8 版。 從 Visual Studio 2019 16.9 版開始還原

在過去,MSVC 允許直接從最高層級底下具有不相符 CV 限定詞的類型繫結參考。 此繫節可能允許修改參考應參考的 const 資料。

Visual Studio 2019 16.0 到 16.8 版的編譯程式會改為建立暫時性,如同當時的標準所需。 之後,標準會追溯變更,使 Visual Studio 2017 和更早版本的行為正確,以及 Visual Studio 2019 16.0 版至 16.8 版的行為錯誤。 因此,此變更從 Visual Studio 2019 16.9 版開始還原。

如需相關變更,請參閱 類似的類型和參考系結

例如,在Visual Studio 2017中,下列程式代碼在沒有警告的情況下編譯。 在 Visual Studio 2019 16.0 到 16.8 版中,編譯程式會引發警告 C4172。 從 Visual Studio 2019 16.9 版開始,程式代碼會再次編譯而不發出警告:

struct X
{
    const void* const& PData() const
    {
        return _pv;
    }

    void* _pv;
};

int main()
{
    X x;
    auto p = x.PData(); // C4172 <func:#1 "?PData@X@@QBEABQBXXZ"> returning address of local variable or temporary
}

來自多載函式的 reinterpret_cast

reinterpret_cast 的引數不是允許多載函式位址的內容之一。 下列程序代碼會在 Visual Studio 2017 中編譯而不會發生錯誤,但在 Visual Studio 2019 中,它會引發錯誤 C2440:

int f(int) { return 1; }
int f(float) { return .1f; }
using fp = int(*)(int);

int main()
{
    fp r = reinterpret_cast<fp>(&f); // C2440: cannot convert from 'overloaded-function' to 'fp'
}

為避免此錯誤,此案例請使用允許的轉換:

int f(int);
int f(float);
using fp = int(*)(int);

int main()
{
    fp r = static_cast<fp>(&f); // or just &f;
}

Lambda 終止

在 C++14 中,Lambda 終止類型不是常值。 此規則的主要的結果是 Lambda 可能不會指派給 constexpr 變數。 下列程序代碼會在 Visual Studio 2017 中編譯而不會發生錯誤,但在 Visual Studio 2019 中,它會引發錯誤 C2127:

int main()
{
    constexpr auto l = [] {}; // C2127 'l': illegal initialization of 'constexpr' entity with a non-constant expression
}

若要避免錯誤,請移除 constexpr 限定符,或將一致性模式變更為 /std:c++17 或更新版本。

std::create_directory 失敗碼

已從 C++20 無條件實作 P1164。 這會變更 std::create_directory,檢查目標在發生故障時是否即為目錄。 以前,所有 ERROR_ALREADY_EXISTS 類型錯誤都會轉換成成功但未建立目錄的程式碼。

operator<<(std::ostream, nullptr_t)

每個 LWG 2221,已新增 operator<<(std::ostream, nullptr_t) 寫入 nullptr 數據流。

更多平行演算法

新的平行版 is_sortedis_sorted_untilis_partitionedset_differenceset_intersectionis_heapis_heap_until

在不可部分完成初始化中修正

P0883「修正不可部分完成初始化」 會變更 std::atomic 為值初始化自主 T 專案,而不是預設初始化它。 使用 Clang/LLVM 與 Microsoft 標準程式庫時即啟用此修正。 目前因使用 Microsoft C++ 編譯器作為 constexpr 處理的 Bug 因應措施而停用。

remove_cvrefremove_cvref_t

已從 P0550 實作 remove_cvrefremove_cvref_t 類型特性。 它們會移除類型中的參考性質和 CV 限定性,但不衰減指標的函式和陣列 (不同於 std::decaystd::decay_t)。

功能測試巨集

P0941R2 - 功能測試巨集已完成,並支援 __has_cpp_attribute。 所有標準模式都支援功能測試巨集。

禁止使用使用者宣告的建構函式彙總

C++20 P1008R1 - 禁止使用使用者宣告建構 函式的匯總已完成。

reinterpret_castconstexpr函式中的

reinterpret_cast 函式中是非法的 constexpr 。 只有在內容中使用 constexpr Microsoft C++ 編譯程式時,Microsoft C++ 編譯程式才會拒絕reinterpret_cast。 在 Visual Studio 2019 的所有語言標準模式中,編譯程式會在函式定義constexpr中正確診斷 reinterpret_cast 。 下列程式代碼現在會產生 C3615:

long long i = 0;
constexpr void f() {
    int* a = reinterpret_cast<int*>(i); // C3615: constexpr function 'f' cannot result in a constant expression
}

若要避免錯誤,請從函式宣告中移除 constexpr 修飾詞。

basic_string 範圍建構函式的正確診斷

在 Visual Studio 2019 中,basic_string 範圍建構函式不再使用 static_cast 隱藏編譯器診斷。 在 Visual Studio 2017 中,下列程式碼在編譯時不會發出警告,但在初始化 out 時,從 wchar_tchar 可能會遺失資料:

std::wstring ws = /* . . . */;
std::string out(ws.begin(), ws.end()); // VS2019 C4244: 'argument': conversion from 'wchar_t' to 'const _Elem', possible loss of data.

Visual Studio 2019 正確地引發警告 C4244。 若要避免警告,您可以初始化 , std::string 如下列範例所示:

std::wstring ws = L"Hello world";
std::string out;
for (wchar_t ch : ws)
{
    out.push_back(static_cast<char>(ch));
}

正確偵測到 += 或 以下或 /ZW-= 以下/clr的呼叫不正確

Visual Studio 2017 中引進了錯誤,導致編譯程式以無訊息方式忽略錯誤,而且不會為 無效的 +=-=/clr 呼叫產生程序代碼。/ZW 在 Visual Studio 2017 中,下列程式碼在編譯時不會引發錯誤,但在 Visual Studio 2019 中會正確引發「錯誤 C2845:

public enum class E { e };

void f(System::String ^s)
{
    s += E::e; // in VS2019 C2845: 'System::String ^': pointer arithmetic not allowed on this type.
}

若要避免此範例中的錯誤,請使用 += 運算元搭配 ToString() 方法: s += E::e.ToString();

內嵌靜態資料成員的初始設定式

現可正確偵測到 inlinestatic constexpr 初始設定式內的無效成員存取。 下列範例會在 Visual Studio 2017 中編譯,但在 /std:c++17 模式或更新版本中,它會引發錯誤 C2248:

struct X
{
    private:
        static inline const int c = 1000;
};

struct Y : X
{
    static inline int d = c; // VS2019 C2248: cannot access private member declared in class 'X'.
};

為避免此錯誤,請將成員 X::c 宣告為受保護:

struct X
{
    protected:
        static inline const int c = 1000;
};

已重新啟用 C4800

MSVC 之前會發出有關隱含轉換為 bool 的效能警告 C4800。 太吵鬧了,無法隱藏,導致我們在Visual Studio 2017 中將其移除。 不過,Visual Studio 2017 在生命週期中獲得許多有助於解決案例問題的意見反應。 我們在 Visual Studio 2019 中重新加入,仔細設計的 C4800,以及說明性的 C4165。 這兩個警告都很容易隱藏:無論是使用明確轉換,還是比對0適當的類型。 C4800 是 預設層級 4 警告,而 C4165 預設為非預設 層級 3 警告。 兩者都可使用 /Wall 編譯器選項探索。

下列範例在 /Wall 下會引發 C4800 和 C4165:

bool test(IUnknown* p)
{
    bool valid = p; // warning C4800: Implicit conversion from 'IUnknown*' to bool. Possible information loss
    IDispatch* d = nullptr;
    HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
    return hr; // warning C4165: 'HRESULT' is being converted to 'bool'; are you sure this is what you want?
}

為避免上例中的警告,您可以撰寫如下的程式碼:

bool test(IUnknown* p)
{
    bool valid = p != nullptr; // OK
    IDispatch* d = nullptr;
    HRESULT hr = p->QueryInterface(__uuidof(IDispatch), reinterpret_cast<void**>(&d));
    return SUCCEEDED(hr);  // OK
}

區域類別成員函式沒有主體

在 Visual Studio 2017 中,只有在明確設定編譯程式選項 /w14822 時,才會引發警告 C4822。 它不會與一起 /Wall顯示。 在 Visual Studio 2019 中,C4822 是預設警告,可讓您在 底下/Wall探索,而不需要明確設定/w14822

void example()
{
    struct A
        {
            int boo(); // warning C4822: Local class member function doesn't have a body
        };
}

包含語句的 if constexpr 函式範本主體

在 或 /std:c++latest下的 /std:c++20 Visual Studio 2019中,具有 if constexpr 語句的範本函式主體已啟用額外的剖析相關檢查。 例如,在Visual Studio 2017中,只有在設定 選項時/permissive-,下列程式代碼才會產生 C7510。 在 Visual Studio 2019 中,即使設定 /permissive 選項,相同的程式碼還是會引發錯誤:

// C7510.cpp
// compile using: cl /EHsc /W4 /permissive /std:c++latest C7510.cpp
#include <iostream>

template <typename T>
int f()
{
    T::Type a; // error C7510: 'Type': use of dependent type name must be prefixed with 'typename'
    // To fix the error, add the 'typename' keyword. Use this declaration instead:
    // typename T::Type a;

    if constexpr (a.val)
    {
        return 1;
    }
    else
    {
        return 2;
    }
}

struct X
{
    using Type = X;
    constexpr static int val = 1;
};

int main()
{
    std::cout << f<X>() << "\n";
}

為避免此錯誤,請將 typename 關鍵字新增至 a 宣告:typename T::Type a;

Lambda 運算式不支援內嵌組譯碼

Microsoft C++ 小組最近知道安全性問題,其中 Lambda 內的內嵌組譯工具使用可能會導致運行時間損毀 ebp (傳回地址緩存器)。 惡意攻擊者可能利用這種情況。 只有 x86 才支援內嵌組譯工具,而內嵌組譯工具與編譯程式其餘部分之間的互動則很差。 鑒於這些事實和問題的性質,此問題最安全的解決方案是不允許 Lambda 運算式內的內嵌組譯工具。

我們所見唯一會在 Lambda 運算式內使用內嵌組譯工具的實際情況,是擷取傳回位址。 在此案例中,您可以擷取所有平台的寄件地址,只要使用編譯器內建 _ReturnAddress() 即可。

下列程序代碼會在 Visual Studio 2017 15.9 和更新版本的 Visual Studio 中產生 C7553

#include <cstdio>

int f()
{
    int y = 1724;
    int x = 0xdeadbeef;

    auto lambda = [&]
    {
        __asm {  // C7553: inline assembler is not supported in a lambda

            mov eax, x
            mov y, eax
        }
    };

    lambda();
    return y;
}

為避免此錯誤,請將組譯碼移入具名函式,如下列範例所示:

#include <cstdio>

void g(int& x, int& y)
{
    __asm {
        mov eax, x
        mov y, eax
    }
}

int f()
{
    int y = 1724;
    int x = 0xdeadbeef;
    auto lambda = [&]
    {
        g(x, y);
    };
    lambda();
    return y;
}

int main()
{
    std::printf("%d\n", f());
}

迭代器偵錯和 std::move_iterator

已教授迭代器偵錯功能正確解除包裝 std::move_iterator。 例如,std::copy(std::move_iterator<std::vector<int>::iterator>, std::move_iterator<std::vector<int>::iterator>, int*) 現可投入 memcpy 快速路徑。

xkeycheck.h> 關鍵詞強制執行的<修正

已修正取代關鍵詞之宏的標準連結庫在 xkeycheck.h> 中的<強制。 連結庫現在會發出偵測到的實際問題關鍵詞,而不是泛型訊息。 它也支援 C++20 的關鍵字,可避免誘騙 IntelliSense 說出隨機的巨集關鍵字。

配置器類型不再予以淘汰

std::allocator<void>std::allocator::size_typestd::allocator::difference_type 不再予以淘汰。

更正縮小字串轉換的警告

已從std::string標準所呼叫的假名static_cast中移除 ,並意外隱藏 C4244 縮小警告。 現在嘗試呼叫 std::string::string(const wchar_t*, const wchar_t*) 時,會正確發出 C4244,以將 縮小 wchar_tchar

檔系統>正確性的各種修正<

  • 修正 std::filesystem::last_write_time 嘗試變更目錄上次寫入時間時失敗。
  • 提供不存在的目標路徑時,std::filesystem::directory_entry 建構函式現在會儲存失敗的結果,而不是擲回例外狀況。
  • std::filesystem::create_directory 的 2 個參數版本已變更為呼叫 1 個參數版本,因為當 existing_p 為符號連結時,基礎 CreateDirectoryExW 函式會使用 copy_symlink
  • 當發現符號連結中斷時,std::filesystem::directory_iterator 不會再失敗。
  • std::filesystem::space 現在接受相對路徑。
  • std::filesystem::path::lexically_relative 不再為句尾斜線困惑,回報為 LWG 3096
  • 因應措施為 CreateSymbolicLinkW 拒絕在 std::filesystem::create_symlink 中有正斜線的路徑。
  • 解決 Windows 10 LTSB 1609 中存在的 POSIX 刪除模式 delete 函式,但無法實際刪除檔案。
  • std::boyer_moore_horspool_searcher 複製建std::boyer_moore_searcher構函式和複製指派運算子現在實際上會複製專案。

Windows 8 和更新版本中的平行演算法

平行演算法程式庫現可正確使用 Windows 8 和更新版本的真實 WaitOnAddress 系列,而不是一律使用 Windows 7 和舊版的假版本。

std::system_category::message() 空白字元

std::system_category::message() 現在會修剪傳回訊息的尾端空格。

std::linear_congruential_engine 除以零

已修正會導致 std::linear_congruential_engine 觸發除以 0 的一些情況。

修正迭代器解除包裝

某些反覆運算器解除包裝機械首次在 Visual Studio 2017 15.8 中公開程式設計人員使用者整合。 本文描述於 C++ 小組部落格文章 STL 功能和 VS 2017 15.8 中的修正。 此機器不再解除包裝衍生自標準連結庫反覆運算器的反覆運算器。 例如,衍生自 std::vector<int>::iterator 的使用者和嘗試自訂行為的使用者,現在都可在呼叫標準程式庫演算法時試取得其自訂的行為,而不是指標的行為。

未排序的容器 reserve 函式現在實際上都會保留以供 N 個元素使用,如 LWG 2156 \(英文\) 中所述。

時間處理

  • 過去,某些已傳遞給並行程式庫的時間值會溢位,例如 condition_variable::wait_for(seconds::max())。 現已修正,溢位過去似乎會以隨機的 29 日循環變更行為 (當基礎 Win32 API 接受的 uint32_t 毫秒溢位時)。

  • <ctime> 標頭現在會正確地宣告 timespec 命名空間 std中的 和 timespec_get ,並在全域命名空間中宣告它們。

容器的各種修正

  • 許多標準連結庫內部容器函式已針對改善的 IntelliSense 體驗而建立 private 。 在 MSVC 的更新版本中,將成員標示為 private 預期的更多修正程式。

  • 我們已修正例外狀況安全性正確性問題,導致節點型容器,例如 listmapunordered_map,變成損毀。 propagate_on_container_copy_assignment在 或 propagate_on_container_move_assignment 重新指派作業期間,我們會使用舊的配置器釋放容器的 sentinel 節點、對舊配置器執行 POCCA/POCMA 指派,然後嘗試從新的配置器取得 sentinel 節點。 如果此配置失敗,容器已損毀。 它甚至無法終結,因為擁有 sentinel 節點是固定的數據結構。 此程式代碼已修正為使用來源容器的配置器建立新的 sentinel 節點,再終結現有的 sentinel 節點。

  • 容器已修正為根據 propagate_on_container_copy_assignmentpropagate_on_container_move_assignmentpropagate_on_container_swap,甚至針對配置器宣告的 is_always_equal 來一律複製/移動/分頁配置器。

  • 已新增容器合併和擷取接受右值容器的成員函式多載。 如需詳細資訊,請參閱 P0083「地圖和集合的接合」

std::basic_istream::read\r\n`` =>處理 \n'

std::basic_istream::read已修正為在處理時暫時不要寫入所提供緩衝區的一部分\r\n\n。 此變更放棄 Visual Studio 2017 15.8 中大小大於 4K 之讀取的效能優勢。 不過,避免每個字元三次虛擬呼叫的效率改善仍存在。

std::bitset 建構函式

std::bitset 建構函式不會再反向讀取大型位元集的一和零。

std::pair::operator= 迴歸

我們已修正在實作 LWG 2729「遺漏 SFINAE onstd::pair::operator=時所引進的指派運算子回歸std::pair。 它現在可以再次正確接受可轉換為 std::pair 的類型。

add_const_t 的非推算內容

我們已修正次要類型特性 Bug,其中 add_const_t 和相關函式應該是非推斷的內容。 換言之,add_const_t 應該是 typename add_const<T>::type 的別名,而非 const T 的別名。

16.1 中的一致性改善

char8_t

P0482r6。 C++20 新增用來表示 UTF-8 字碼單位的新字元類型。 C++20 的 u8 字串常值具有類型 const char8_t[N] 而非 const char[N],這是舊例。 N2231 \(英文\) 已針對 C 標準建議類似的變更。 char8_t P1423r3會提供回溯相容性補救的建議。 當您指定/Zc:char8_t編譯程式選項時,Microsoft C++ 編譯程式會在 Visual Studio 2019 16.1 版中新增對 的支援char8_t。 它可透過 /Zc:char8_t-還原為 C++17 行為。 支援 IntelliSense 的 EDG 編譯程式尚不支援 Visual Studio 2019 16.1 版。 您可能會看到不影響實際編譯的虛假 IntelliSense 錯誤。

範例

const char* s = u8"Hello"; // C++17
const char8_t* s = u8"Hello"; // C++20

std::type_identity Metafunction 和 std::identity 函式物件

P0887R1 type_identity. 已移除淘汰的 std::identity 類別範本副檔名,並已經以 C++20 std::type_identity metafunction 和 std::identity 函式物件取代。 這兩者僅適用於 /std:c++latest/std:c++20 在 Visual Studio 2019 16.11 版和更新版本中)。

下列範例會產生 Visual Studio 2017 中適用於 (定義於 <type_traits> 中) 的淘汰警告 C4996 std::identity

#include <type_traits>

using T = std::identity<int>::type;
T x, y = std::identity<T>{}(x);
int i = 42;
long j = std::identity<long>{}(i);

下列範例示範如何使用新的 std::identity (在功能>中<定義) 與新的 std::type_identity

#include <type_traits>
#include <functional>

using T = std::type_identity<int>::type;
T x, y = std::identity{}(x);
int i = 42;
long j = static_cast<long>(i);

泛型 Lambda 的語法檢查

新的 Lambda 處理器可在一般 Lambda 中、 /std:c++latest 在 Visual/std:c++20 Studio 2019 16.11 版和更新版本中啟用一些一致性模式語法檢查,或在 Visual Studio 2019 16.9 版或更新版本中使用的任何其他語言模式 /Zc:lambda 檢查(先前可從 /experimental:newLambdaProcessor Visual Studio 2019 16.3 版開始)。

舊版 Lambda 處理器會編譯此範例,但新的 Lambda 處理器會產生錯誤 C2760:

void f() {
    auto a = [](auto arg) {
        decltype(arg)::Type t; // C2760 syntax error: unexpected token 'identifier', expected ';'
    };
}

此範例顯示編譯程式現在強制執行的正確語法:

void f() {
    auto a = [](auto arg) {
        typename decltype(arg)::Type t;
    };
}

函式呼叫的引數相依查閱

P0846R0 (C++20) 透過具有明確範本自變數之函數調用表達式的自變數相依查閱,增加尋找函式範本的能力。 /std:c++latest 需要 (或在 /std:c++20 Visual Studio 2019 16.11 版和更新版本中)。

指定的初始化

P0329R4 (C++20) 指定的初始化允許使用 Type t { .member = expr } 語法在匯總初始化中選取特定成員。 /std:c++latest 需要 (或在 /std:c++20 Visual Studio 2019 16.11 版和更新版本中)。

列舉轉換成其固定基礎類型的排名

編譯程式現在會根據 N4800 11.3.3.2 排名隱含轉換序列來排名列舉轉換 (4.2):

  • 如果兩者不同,則升級列舉的轉換,其基礎型別固定為其基礎類型,比升階為升級基礎類型的轉換更好。

Visual Studio 2019 16.1 版之前未正確實作此轉換排名。 符合行為可能會變更多載解析行為,或公開先前未偵測到多載解析行為的模棱兩可。

此編譯程式行為變更適用於所有 /std 模式,而且同時是來源和二進位中斷性變更。

下列範例示範編譯程序行為如何在 16.1 和更新版本中變更:

#include <type_traits>

enum E : unsigned char { e };

int f(unsigned int)
{
    return 1;
}

int f(unsigned char)
{
    return 2;
}

struct A {};
struct B : public A {};

int f(unsigned int, const B&)
{
    return 3;
}

int f(unsigned char, const A&)
{
    return 4;
}

int main()
{
    // Calls f(unsigned char) in 16.1 and later. Called f(unsigned int) in earlier versions.
    // The conversion from 'E' to the fixed underlying type 'unsigned char' is better than the
    // conversion from 'E' to the promoted type 'unsigned int'.
    f(e);
  
    // Error C2666. This call is ambiguous, but previously called f(unsigned int, const B&). 
    f(e, B{});
}

更新的新標準程式庫函式 (C++20)

  • 用於 basic_stringbasic_string_viewstarts_with()ends_with()
  • 關聯容器的 contains()
  • listforward_listremove()remove_if()unique() 現在會傳回 size_type
  • shift_left()shift_right() 已新增至 <演算法>。

16.2 中的一致性改善

noexceptconstexpr 函式

constexpr 在常數表示式中使用 時,預設不會再考慮 noexcept 函式。 此行為變更來自核心工作組 (CWG) CWG 1351 的解決,並在 中 /permissive-啟用。 下列範例會在 Visual Studio 2019 16.1 版和更早版本中編譯,但在 Visual Studio 2019 16.2 版中產生 C2338:

constexpr int f() { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept"); // C2338 in 16.2
}

若要修正錯誤,請將 noexcept 表達式新增至函式宣告:

constexpr int f() noexcept { return 0; }

int main() {
    static_assert(noexcept(f()), "f should be noexcept");
}

具有不同列舉類型的二進位表達式

C++20 已取代操作數上的一般算術轉換,其中:

  • 一個操作數是列舉型別,和

  • 另一個是不同的列舉型別或浮點類型。

如需詳細資訊,請參閱 P1120R0

在 Visual Studio 2019 16.2 版和更新版本中,下列程式代碼會在啟用編譯程式選項時 /std:c++latest 產生層級 4 C5054 警告(/std:c++20 在 Visual Studio 2019 16.11 版和更新版本中):

enum E1 { a };
enum E2 { b };
int main() {
    int i = a | b; // warning C5054: operator '|': deprecated between enumerations of different types
}

若要避免警告,請使用 static_cast 來轉換第二個操作數:

enum E1 { a };
enum E2 { b };
int main() {
  int i = a | static_cast<int>(b);
}

啟用編譯程式選項時 /std:c++latest ,在列舉和浮點類型之間使用二進位運算現在是層級 1 C5055 警告(/std:c++20 在 Visual Studio 2019 16.11 版和更新版本中):

enum E1 { a };
int main() {
  double i = a * 1.1;
}

若要避免警告,請使用 static_cast 來轉換第二個操作數:

enum E1 { a };
int main() {
   double i = static_cast<int>(a) * 1.1;
}

數位的相等和關係比較

在 C++20 (P1120R0) 中,陣列類型的兩個操作數之間的相等和關係比較已被取代。 換句話說,兩個陣列之間的比較作業(儘管排名和範圍相似性)現在是警告。 在 Visual Studio 2019 16.2 版和更新版本中,下列程式代碼會在啟用編譯程式選項時 /std:c++latest 產生層級 1 警告 C5056 (/std:c++20 在 Visual Studio 2019 16.11 版和更新版本中):

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (a == b) { return 1; } // warning C5056: operator '==': deprecated for array types
}

若要避免警告,您可以比較第一個元素的位址:

int main() {
    int a[] = { 1, 2, 3 };
    int b[] = { 1, 2, 3 };
    if (&a[0] == &b[0]) { return 1; }
}

若要判斷兩個陣列的內容是否相等,請使用 函 std::equal 式:

std::equal(std::begin(a), std::end(a), std::begin(b), std::end(b));

定義宇宙飛船運算符對 == 和的影響 !=

單靠宇宙飛船運算符 (<=>) 的定義將不再重寫涉及==!=的運算式,除非宇宙飛船運算符標示為 = defaultP1185R2)。 下列範例會在 Visual Studio 2019 RTW 和 16.1 版中編譯,但在 Visual Studio 2019 16.2 版中產生 C2678:

#include <compare>

struct S {
  int a;
  auto operator<=>(const S& rhs) const {
    return a <=> rhs.a;
  }
};
bool eq(const S& lhs, const S& rhs) {
  return lhs == rhs; // error C2676
}
bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs; // error C2676
}

若要避免錯誤,請定義 operator== 或將其宣告為預設值:

#include <compare>

struct S {
  int a;
  auto operator<=>(const S& rhs) const {
    return a <=> rhs.a;
  }
  bool operator==(const S&) const = default;
};
bool eq(const S& lhs, const S& rhs) {
  return lhs == rhs;
}
bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;
}

標準連結庫改善

  • <具有固定/科學精確度的 charconv>to_chars() 。 (一般精確度目前計劃為16.4。
  • P0020R6:atomic<float>、、、 atomic<double>atomic<long double>
  • P0463R1:endian
  • P0482R6:連結庫支援char8_t
  • P0600R1:[[nodiscard]]針對 STL,第 1 部分
  • P0653R2:to_address()
  • P0754R2:<版本>
  • P0771R1:noexcept針對std::function的移動建構函式

關聯容器的 Const 比較子

mapmultisetmultimapset搜尋和插入的程式代碼已合併,以縮小程式代碼大小。 插入作業現在會呼叫比較函式上的 const 小於比較,其方式與先前的搜尋作業相同。 下列程序代碼會在 Visual Studio 2019 16.1 版和更早版本中編譯,但在 Visual Studio 2019 16.2 版中會引發 C3848:

#include <iostream>
#include <map>

using namespace std;

struct K
{
   int a;
   string b = "label";
};

struct Comparer  {
   bool operator() (K a, K b) {
      return a.a < b.a;
   }
};

map<K, double, Comparer> m;

K const s1{1};
K const s2{2};
K const s3{3};

int main() {

   m.emplace(s1, 1.08);
   m.emplace(s2, 3.14);
   m.emplace(s3, 5.21);

}

若要避免錯誤,請讓比較運算子 const

struct Comparer  {
   bool operator() (K a, K b) const {
      return a.a < b.a;
   }
};

Visual Studio 2019 16.3 版的一致性改善

已移除的數據流擷 char* 取運算符

已移除指針對字元的數據流擷取運算符,並取代為字元陣列的擷取運算子(每個 P0487R1)。 WG21 會將移除的多載視為不安全。 在 /std:c++20/std:c++latest 模式中,下列範例現在會產生 C2679:

// stream_extraction.cpp
// compile by using: cl /std:c++latest stream_extraction.cpp

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    char* p = x;
    std::cin >> std::setw(42);
    std::cin >> p;  // C2679: binary '>>': no operator found which takes a right-hand operand of type 'char *' (or there is no acceptable conversion)
}

若要避免錯誤,請使用擷取運算子搭配 char[] 變數:

#include <iostream>
#include <iomanip>

int main() {
    char x[42];
    std::cin >> std::setw(42);
    std::cin >> x;  // OK
}

新的關鍵詞 requiresconcept

新的關鍵詞 requires ,並 concept 已新增至 Microsoft C++ 編譯程式。 如果您嘗試在 或 /std:c++latest 模式中使用其中一個做為標識符/std:c++20,編譯程式會引發 C2059 來指出語法錯誤。

不允許類型名稱的建構函式

在此情況下,編譯程式不再將建構函式名稱視為 injected-class-names:當建構函式名稱出現在類別範本特製化別名之後的限定名稱時。 先前,建構函式可作為宣告其他實體的類型名稱。 下列範例現在會產生 C3646:

#include <chrono>

class Foo {
   std::chrono::milliseconds::duration TotalDuration{}; // C3646: 'TotalDuration': unknown override specifier
};

若要避免錯誤,請宣告 TotalDuration ,如下所示:

#include <chrono>

class Foo {
  std::chrono::milliseconds TotalDuration {};
};

更嚴格的函式 extern "C" 檢查

如果函 extern "C" 式在不同的命名空間中宣告,舊版的 Microsoft C++ 編譯程式並未檢查宣告是否相容。 在 Visual Studio 2019 16.3 版和更新版本中,編譯程式會檢查相容性。 在 /permissive- 模式中,下列程式代碼會產生錯誤 C2371 和 C2733:

using BOOL = int;

namespace N
{
   extern "C" void f(int, int, int, bool);
}

void g()
{
   N::f(0, 1, 2, false);
}

extern "C" void f(int, int, int, BOOL){}
    // C2116: 'N::f': function parameter lists do not match between declarations
    // C2733: 'f': you cannot overload a function with 'extern "C"' linkage

若要避免上一個範例中的錯誤,請在 bool 的兩個宣告f中使用 ,BOOL而不是一致。

標準連結庫改善

已移除非標準標頭 <stdexcpt.h> 和 <typeinfo.h> 。 包含它們的程式代碼應該分別包含標準標頭 <例外狀況> 和 <typeinfo>。

Visual Studio 2019 16.4 版的一致性改善

在中更妥善地強制執行限定標識碼的雙階段名稱查閱 /permissive-

兩階段名稱查閱需要讓範本能夠在定義時看到範本主體中使用的非相依名稱。 先前,當範本具現化時,可能會找到這類名稱。 這項變更可讓您更輕鬆地在旗標下的 /permissive- MSVC 中撰寫可攜式且符合規範的程式代碼。

在設定旗標的 /permissive- Visual Studio 2019 16.4 版中,下列範例會產生錯誤,因為在 N::f 定義範本時 f<T> 看不到:

template <class T>
int f() {
    return N::f() + T{}; // error C2039: 'f': is not a member of 'N'
}

namespace N {
    int f() { return 42; }
}

通常,可以藉由包含遺漏標頭或轉送宣告函式或變數來修正此錯誤,如下列範例所示:

namespace N {
    int f();
}

template <class T>
int f() {
    return N::f() + T{};
}

namespace N {
    int f() { return 42; }
}

將整數常數表示式隱含轉換成 Null 指標

MSVC 編譯程式現在會在一致性模式中實作 CWG 問題 903/permissive- 此規則不允許將整數常數表達式的隱含轉換(整數常值 』0'除外)轉換為 Null 指標常數。 下列範例會以一致性模式產生 C2440:

int* f(bool* p) {
    p = false; // error C2440: '=': cannot convert from 'bool' to 'bool *'
    p = 0; // OK
    return false; // error C2440: 'return': cannot convert from 'bool' to 'int *'
}

若要修正錯誤,請使用 nullptr 而非 false。 仍允許常值 0:

int* f(bool* p) {
    p = nullptr; // OK
    p = 0; // OK
    return nullptr; // OK
}

整數常值類型的標準規則

在一致性模式中, /permissive-MSVC 會使用整數常值類型的標準規則。 小數常值太大而無法容納在 中 signed int ,先前指定的型別 unsigned int為 。 現在,這類常值會得到下一個最大的 signed 整數類型, long long。 此外,具有 『ll』 後置詞的常值,其太大而無法放入類型中 signed ,則會指定類型 unsigned long long

這項變更可能會導致產生不同的警告診斷,以及常值算術運算的行為差異。

下列範例顯示 Visual Studio 2019 16.4 版的新行為。 變數 i 現在的類型為 unsigned int,因此會引發警告。 變數 j 的高順序位會設定為 0。

void f(int r) {
    int i = 2964557531; // warning C4309: truncation of constant value
    long long j = 0x8000000000000000ll >> r; // literal is now unsigned, shift will fill high-order bits with 0
}

下列範例示範如何保留舊的行為,並避免警告和運行時間行為變更:

void f(int r) {
int i = 2964557531u; // OK
long long j = (long long)0x8000000000000000ll >> r; // shift will keep high-order bits
}

陰影範本參數的函式參數

MSVC 編譯程式現在會在函式參數遮蔽範本參數時引發錯誤:

template<typename T>
void f(T* buffer, int size, int& size_read);

template<typename T, int Size>
void f(T(&buffer)[Size], int& Size) // error C7576: declaration of 'Size' shadows a template parameter
{
    return f(buffer, Size, Size);
}

若要修正錯誤,請變更其中一個參數的名稱:

template<typename T>
void f(T* buffer, int size, int& size_read);

template<typename T, int Size>
void f(T (&buffer)[Size], int& size_read)
{
    return f(buffer, Size, size_read);
}

類型特性的使用者提供特製化

與 Standard 的 meta.rqmts 子程式一致,MSVC 編譯程式現在會在找到命名空間中其中一個指定type_traits範本std的使用者定義特製化時引發錯誤。 除非另有指定,否則這類特製化會導致未定義的行為。 下列範例有未定義的行為,因為它違反規則,而且 static_assert 失敗並出現錯誤 C2338。

#include <type_traits>
struct S;

template<>
struct std::is_fundamental<S> : std::true_type {};

static_assert(std::is_fundamental<S>::value, "fail");

若要避免錯誤,請定義繼承自慣用 type_trait的結構,並特製化:

#include <type_traits>

struct S;

template<typename T>
struct my_is_fundamental : std::is_fundamental<T> {};

template<>
struct my_is_fundamental<S> : std::true_type { };

static_assert(my_is_fundamental<S>::value, "fail");

編譯程式提供的比較運算符變更

MSVC 編譯程式現在會在啟用 或 /std:c++latest 選項時/std:c++20,針對每個P1630R1的比較運算符實作下列變更:

如果表達式包含不是bool的傳回型別,編譯程式就不會再使用 operator== 重寫表達式。 下列程式代碼現在會產生錯誤 C2088:

struct U {
    operator bool() const;
};

struct S {
    U operator==(const S&) const;
};

bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;  // C2088: '!=': illegal for struct
}

若要避免錯誤,您必須明確定義所需的運算子:

struct U {
    operator bool() const;
};

struct S {
    U operator==(const S&) const;
    U operator!=(const S&) const;
};

bool neq(const S& lhs, const S& rhs) {
    return lhs != rhs;
}

如果編譯程式是等位類別的成員,編譯程式就不會再定義預設的比較運算符。 下列範例現在會產生錯誤 C2120:

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const = default;
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

若要避免錯誤,請為 運算子定義主體:

#include <compare>

union S {
    int a;
    char b;
    auto operator<=>(const S&) const { ... }
};

bool lt(const S& lhs, const S& rhs) {
    return lhs < rhs;
}

如果類別包含參考成員,編譯程式將不再定義預設的比較運算符。 下列程式代碼現在會產生錯誤 C2120:

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const = default;
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

若要避免錯誤,請為 運算子定義主體:

#include <compare>

struct U {
    int& a;
    auto operator<=>(const U&) const { ... };
};

bool lt(const U& lhs, const U& rhs) {
    return lhs < rhs;
}

Visual Studio 2019 16.5 版的一致性改善

沒有初始化表達式的明確特製化宣告不是定義

在下 /permissive-,MSVC 現在會強制執行沒有初始化表達式的明確特製化宣告不是定義的標準規則。 先前,宣告會被視為具有預設初始化表達式的定義。 效果在連結時是可觀察到的,因為根據此行為的程式現在可能已有無法解析的符號。 這個範例現在會產生錯誤:

template <typename> struct S {
    static int a;
};

// In permissive-, this declaration isn't a definition, and the program won't link.
template <> int S<char>::a;

int main() {
    return S<char>::a;
}
error LNK2019: unresolved external symbol "public: static int S<char>::a" (?a@?$S@D@@2HA) referenced in function _main at link time.

若要解決此問題,請新增初始化表達式:

template <typename> struct S {
    static int a;
};

// Add an initializer for the declaration to be a definition.
template <> int S<char>::a{};

int main() {
    return S<char>::a;
}

預處理器輸出會保留換行符

實驗性預處理器現在會在使用 /P/E/experimental:preprocessor時保留換行符和空格符。

假設此範例來源,

#define m()
line m(
) line

先前輸出 /E 為:

line line
#line 2

的新輸出 /E 現在是:

line
 line

importmodule 關鍵詞與內容相關

每個P1857R1importmodule預處理器指示詞都有其語法的新限制。 此範例不再編譯:

import // Invalid
m;     // error C2146: syntax error: missing ';' before identifier 'm'

若要解決此問題,請將匯入保留在同一行:

import m; // OK

std::weak_equality拿掉和std::strong_equality

P1959R0的合併需要編譯程式移除 和 型別的行為和參考std::strong_equalitystd::weak_equality

此範例中的程式代碼不再編譯:

#include <compare>

struct S {
    std::strong_equality operator<=>(const S&) const = default;
};

void f() {
    nullptr<=>nullptr;
    &f <=> &f;
    &S::operator<=> <=> &S::operator<=>;
}

這個範例現在會導致下列錯誤:

error C2039: 'strong_equality': is not a member of 'std'
error C2143: syntax error: missing ';' before '<=>'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C7546: binary operator '<=>': unsupported operand types 'nullptr' and 'nullptr'
error C7546: binary operator '<=>': unsupported operand types 'void (__cdecl *)(void)' and 'void (__cdecl *)(void)'
error C7546: binary operator '<=>': unsupported operand types 'int (__thiscall S::* )(const S &) const' and 'int (__thiscall S::* )(const S &) const'

若要解決此問題,請更新以偏好內建關係運算符,並取代已移除的類型:

#include <compare>

struct S {
    std::strong_ordering operator<=>(const S&) const = default; // prefer 'std::strong_ordering'
};

void f() {
    nullptr != nullptr; // use pre-existing builtin operator != or ==.
    &f != &f;
    &S::operator<=> != &S::operator<=>;
}

TLS 防護變更

先前,DLL 中的線程局部變數未正確初始化。 除了在載入 DLL 的線程上,在載入 DLL 之前先在存在的線程上使用之前,不會先初始化它們。 此缺陷現已修正。 這類 DLL 中的線程局部變數會在在這類線程上第一次使用之前立即初始化。

使用編譯程式選項可以停用 /Zc:tlsGuards- 針對使用線程局部變數進行初始化的新行為。 或者,將屬性新增 [[msvc:no_tls_guard]] 至特定線程局部變數。

更佳診斷已刪除函式的呼叫

我們的編譯程式對先前已刪除函式的呼叫更寬鬆。 例如,如果呼叫發生在範本主體的內容中,我們不會診斷呼叫。 此外,如果有多個已刪除函式呼叫的實例,我們只會發出一個診斷。 現在我們會為每個它們發出診斷。

新行為的其中一個後果可能會產生一個小的重大變更:如果不需要程式代碼產生,呼叫已刪除函式的程式代碼將不會被診斷。 現在我們會在前面診斷它。

這個範例顯示現在會產生錯誤的程式代碼:

struct S {
  S() = delete;
  S(int) { }
};

struct U {
  U() = delete;
  U(int i): s{ i } { }

  S s{};
};

U u{ 0 };
error C2280: 'S::S(void)': attempting to reference a deleted function
note: see declaration of 'S::S'
note: 'S::S(void)': function was explicitly deleted

若要解決此問題,請移除已刪除函式的呼叫:

struct S {
  S() = delete;
  S(int) { }
};

struct U {
  U() = delete;
  U(int i): s{ i } { }

  S s;  // Do not call the deleted ctor of 'S'.
};

U u{ 0 };

Visual Studio 2019 16.6 版中的一致性改善

標準連結庫數據流會拒絕插入編碼錯誤的字元類型

傳統上,將 wchar_t 插入 至 std::ostream,並將 或 char32_tstd::ostream 插入 char16_tstd::wostream,會輸出其整數值。 插入這些字元類型的指標會輸出指標值。 程序設計人員找不到兩種直覺式。 它們通常會預期標準連結庫會改為轉碼字元或以 Null 結尾的字元字串,並輸出結果。

C++20 提案 P1423R3 會針對數據流和字元指標類型的這些組合新增已刪除的數據流插入運算元多載。 在 或 /std:c++latest/std:c++20,多載會使這些插入格式不正確,而不是以非預期的方式行事。 當找到錯誤 C2280 時,編譯程式就會引發錯誤。 您可以定義要1還原舊行為的「逸出艙口」宏_HAS_STREAM_INSERTION_OPERATORS_DELETED_IN_CXX20。 (提案也會刪除的 char8_t數據流插入運算元。當我們新增 char8_t 支援時,我們的標準連結庫實作類似的多載,因此「錯誤」的行為從未可供 使用 char8_t

此範例顯示此變更的行為:

#include <iostream>
int main() {
    const wchar_t cw = L'x', *pw = L"meow";
    const char16_t c16 = u'x', *p16 = u"meow";
    const char32_t c32 = U'x', *p32 = U"meow";
    std::cout << cw << ' ' << pw << '\n';
    std::cout << c16 << ' ' << p16 << '\n';
    std::cout << c32 << ' ' << p32 << '\n';
    std::wcout << c16 << ' ' << p16 << '\n';
    std::wcout << c32 << ' ' << p32 << '\n';
}

程式代碼現在會產生下列診斷訊息:

error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,wchar_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<char,std::char_traits<char>> &std::<<<std::char_traits<char>>(std::basic_ostream<char,std::char_traits<char>> &,char32_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char16_t)': attempting to reference a deleted function
error C2280: 'std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &std::<<<std::char_traits<wchar_t>>(std::basic_ostream<wchar_t,std::char_traits<wchar_t>> &,char32_t)': attempting to reference a deleted function

您可以將字元類型 unsigned int轉換成, 或指標到字元類型, const void*以在所有語言模式中達到舊行為的效果:

#include <iostream>
int main() {
    const wchar_t cw = L'x', *pw = L"meow";
    const char16_t c16 = u'x', *p16 = u"meow";
    const char32_t c32 = U'x', *p32 = U"meow";
    std::cout << (unsigned)cw << ' ' << (const void*)pw << '\n'; // Outputs "120 0052B1C0"
    std::cout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
    std::cout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
    std::wcout << (unsigned)c16 << ' ' << (const void*)p16 << '\n'; // Outputs "120 0052B1CC"
    std::wcout << (unsigned)c32 << ' ' << (const void*)p32 << '\n'; // Outputs "120 0052B1D8"
}

已變更的傳 std::pow() 回型別 std::complex

先前,函式範本 std::pow() 傳回類型的升級規則 MSVC 實作不正確。 例如,先前 pow(complex<float>, int)complex<float>回 。 現在它會正確傳 complex<double>回 。 已針對 Visual Studio 2019 16.6 版中的所有標準模式無條件實作修正。

這項變更可能會導致編譯程序錯誤。 例如,您先前可以乘 pow(complex<float>, int)float。 因為 complex<T> operator* 預期具有相同類型的自變數,下列範例現在會發出編譯程序錯誤 C2676:

// pow_error.cpp
// compile by using: cl /EHsc /nologo /W4 pow_error.cpp
#include <complex>

int main() {
    std::complex<float> cf(2.0f, 0.0f);
    (void) (std::pow(cf, -1) * 3.0f);
}
pow_error.cpp(7): error C2676: binary '*': 'std::complex<double>' does not define this operator or a conversion to a type acceptable to the predefined operator

有許多可能的修正:

  • 將乘法的類型 float 變更為 double。 這個自變數可以直接轉換成 complex<double> ,以符合 所 pow傳回的類型。

  • 將的結果 pow 縮小為 complex<float> ,方法是說 complex<float>{pow(ARG, ARG)}。 然後,您可以繼續乘以 float 值。

  • 傳遞 ,int而不是 傳遞floatpow。 此作業可能會變慢。

  • 在某些情況下,您可以完全避免 pow 。 例如, pow(cf, -1) 可以取代除法。

switch C 的警告

在 Visual Studio 2019 16.6 版和更新版本中,編譯程式會針對編譯為 C 的程式代碼實作一些預先存在的 C++ 警告。下列警告現在會在不同層級啟用:C4060、C4061、C4062、C4063、C4064、C4065、C4808 和 C4809。 C 預設會停用 C4065 和 C4060 的警告。

遺漏 case 語句、未定義 enum和錯誤 bool switch 語句的警告會觸發 (也就是包含太多案例的語句)。 例如:

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
        default: break; // C4809: switch statement has redundant 'default' label;
                        // all possible 'case' labels are given
    }
}

若要修正此程式代碼,請移除備援 default 案例:

#include <stdbool.h>

int main() {
    bool b = true;
    switch (b) {
        case true: break;
        case false: break;
    }
}

宣告中的 typedef 未命名類別

在 Visual Studio 2019 16.6 版和更新版本中,宣告的行為 typedef 已限制為符合 P1766R1。 透過此更新,宣告內的 typedef 未命名類別不能有下列任何成員:

  • 沒有預設成員初始化表達式的非靜態數據成員,
  • 成員類別或
  • 成員列舉。

相同的限制會以遞歸方式套用至每個巢狀類別。 此限制旨在確保結構簡單,這些結構具有 typedef 連結用途的名稱。 在編譯程式取得 typedef 連結名稱之前,它們必須足夠簡單,因此不需要鏈接計算。

這項變更會影響編譯程式的所有標準模式。 在預設 (/std:c++14) 和 /std:c++17 模式中,編譯程式會針對不符合規範的程式代碼發出警告 C5208。 如果 /permissive- 已指定,編譯程式會發出警告 C5208 做為 錯誤, /std:c++14 並在 下 /std:c++17發出錯誤 C7626。 編譯程式會在指定 或 /std:c++latest/std:c++20,針對不符合規範的程式代碼發出錯誤 C7626。

下列範例顯示未命名結構中不再允許的建構。 根據指定的標準模式,會發出 C5208 或 C7626 錯誤或警告:

struct B { };
typedef struct : B { // inheriting from 'B'; ill-formed
    void f(); // ill-formed
    static int i; // ill-formed
    struct U {
        void f(); // nested class has non-data member; ill-formed
    };
    int j = 10; // default member initializer; ill-formed
} S;

您可以藉由為未命名的類別提供名稱來修正上述程式代碼:

struct B { };
typedef struct S_ : B {
    void f();
    static int i;
    struct U {
        void f();
    };
    int j = 10;
} S;

C++/CLI 中的預設自變數匯入

越來越多的 API 在 .NET Core 中具有預設自變數。 因此,我們現在支援 C++/CLI 中的預設自變數匯入。 這項變更可能會中斷宣告多個多載的現有程序代碼,如下列範例所示:

public class R {
    public void Func(string s) {}   // overload 1
    public void Func(string s, string s2 = "") {} // overload 2;
}

當這個類別匯入 C++/CLI 時,呼叫其中一個多載會造成錯誤:

    (gcnew R)->Func("abc"); // error C2668: 'R::Func' ambiguous call to overloaded function

編譯程式會發出錯誤 C2668,因為兩個多載都符合這個自變數清單。 在第二個多載中,預設自變數會填入第二個自變數。 若要解決此問題,您可以刪除備援多載 (1)。 或者,使用完整的自變數清單,並明確提供預設自變數。

Visual Studio 2019 16.7 版的一致性改善

是可複製的定義

C++20 已變更 的定義 是簡單可複製的。 當類別具有具有限定類型的非靜態數據成員 volatile 時,它不再表示任何編譯程式產生的複製或移動建構函式,或複製或移動指派運算符都是非簡單的。 C++ 標準委員會會將這項變更追溯地套用為瑕疵報告。 在 MSVC 中,編譯程式行為不會以不同的語言模式變更,例如 /std:c++14/std:c++latest

以下是新行為的範例:

#include <type_traits>

struct S
{
    volatile int m;
};

static_assert(std::is_trivially_copyable_v<S>, "Meow!");

在 Visual Studio 2019 16.7 版之前,此程式代碼不會在 MSVC 版本中編譯。 默認有一個編譯程式警告,可用來偵測此變更。 如果您使用 編譯上述 cl /W4 /w45220程序代碼,您會看到下列警告:

warning C5220: `'S::m': a non-static data member with a volatile qualified type no longer implies that compiler generated copy/move constructors and copy/move assignment operators are non trivial`

要縮小的成員指標和字串常值轉換bool

C++ 標準委員會最近通過了瑕疵報告P1957R2,該報告視為T*bool縮小轉換。 MSVC 已修正其實作中的 Bug,其先前會診斷 T*bool 縮小,但未診斷字串常值 bool 轉換成 或 將指標 bool轉換為 。

下列程式在 Visual Studio 2019 16.7 版中格式不正確:

struct X { bool b; };
void f(X);

int main() {
    f(X { "whoops?" }); // error: conversion from 'const char [8]' to 'bool' requires a narrowing conversion

    int (X::* p) = nullptr;
    f(X { p }); // error: conversion from 'int X::*' to 'bool' requires a narrowing conversion
}

若要更正此程式代碼,請將明確的比較新增至 nullptr,或避免縮小轉換格式不正確的內容:

struct X { bool b; };
void f(X);

int main() {
    f(X { "whoops?" != nullptr }); // Absurd, but OK

    int (X::* p) = nullptr;
    f(X { p != nullptr }); // OK
}

nullptr_t 只能轉換成 bool 直接初始化

在 C++11 中,nullptr只能轉換成bool直接轉換;例如,當您使用大括號初始化表達式清單初始化 bool 時。 MSVC 從未強制執行此限制。 MSVC 現在會在下 /permissive-實作 規則。 隱含轉換現在會診斷為格式不正確。 仍允許內容轉換至 bool ,因為直接初始化 bool b(nullptr) 有效。

在大部分情況下,您可以使用 取代 nullptrfalse來修正錯誤,如下列範例所示:

struct S { bool b; };
void g(bool);
bool h() { return nullptr; } // error, should be 'return false;'

int main() {
    bool b1 = nullptr; // error: cannot convert from 'nullptr' to 'bool'
    S s { nullptr }; // error: cannot convert from 'nullptr' to 'bool'
    g(nullptr); // error: cannot convert argument 1 from 'nullptr' to 'bool'

    bool b2 { nullptr }; // OK: Direct-initialization
    if (!nullptr) {} // OK: Contextual conversion to bool
}

符合具有遺漏初始化表達式之數位初始化的初始化行為

先前,MSVC 對於遺漏初始化表達式的陣列初始化行為不一致。 MSVC 一律會針對沒有初始化表達式的每個數位專案呼叫預設建構函式。 標準行為是使用空白大括號-initializer-list ({}) 初始化每個元素。 空白大括號初始化表達式清單的初始化內容是 copy-initialization,不允許呼叫明確的建構函式。 也可能有運行時間差異,因為 使用 {} 來初始化 可能會呼叫採用 std::initializer_list的建構函式,而不是預設建構函式。 符合的行為會在下 /permissive-啟用。

以下是已變更行為的範例:

struct B {
    explicit B() {}
};

void f() {
    B b1[1]{}; // Error in /permissive-, because aggregate init calls explicit ctor
    B b2[1]; // OK: calls default ctor for each array element
}

已正確排序具有多載名稱的類別成員初始化

當類型名稱也多載為數據成員的名稱時,我們識別出類別數據成員內部表示法中的 Bug。 這個錯誤導致匯總初始化和成員初始化順序不一致。 產生的初始化程式代碼現在正確無誤。 不過,這項變更可能會導致來源中不小心依賴錯誤排序成員的錯誤或警告,如下列範例所示:

// Compiling with /w15038 now gives:
// warning C5038: data member 'Outer::Inner' will be initialized after data member 'Outer::v'
struct Outer {
    Outer(int i, int j) : Inner{ i }, v{ j } {}

    struct Inner { int x; };
    int v;
    Inner Inner; // 'Inner' is both a type name and data member name in the same scope
};

在舊版中,建構函式會在數據成員 之前不正確地初始化數據成員Innerv。 (C++ 標準需要與成員宣告順序相同的初始化順序。 既然產生的程式代碼遵循標準,則 member-init-list 順序不依序。 編譯程式會產生這個範例的警告。 若要修正此問題,請重新排序 member-initializer-list 以反映宣告順序。

涉及整數多載和 long 自變數的多載解析

C++ 標準需要將 排序 longint 轉換做為標準轉換。 先前的 MSVC 編譯程式不正確地將它列為整數提升,這會針對多載解析排名較高。 此排名可能會導致多載解析在視為模棱兩可時成功解析。

編譯程式現在會在模式中 /permissive- 正確考慮排名。 無效的程式代碼會正確診斷,如下列範例所示:

void f(long long);
void f(int);

int main() {
    long x {};
    f(x); // error: 'f': ambiguous call to overloaded function
    f(static_cast<int>(x)); // OK
}

您可以透過數種方式來修正此問題:

  • 在呼叫站臺上,將傳遞自變數的類型變更為 int。 您可以變更變數類型,或轉換它。

  • 如果有許多呼叫月臺,您可以新增另一 long 個採用 自變數的多載。 在此函式中,將 自變數 int 轉換為多載。

搭配內部連結使用未定義的變數

Visual Studio 2019 16.7 版之前的 MSVC 版本接受使用宣告 extern 為內部連結且未定義的變數。 這類變數無法在任何其他轉譯單位中定義,而且無法形成有效的程式。 編譯程式現在會在編譯時期診斷此案例。 此錯誤類似於未定義的靜態函式的錯誤。

namespace {
    extern int x; // Not a definition, but has internal linkage because of the anonymous namespace
}

int main()
{
    return x; // Use of 'x' that no other translation unit can possibly define.
}

此程式先前編譯不正確且已連結,但現在會發出錯誤 C7631。

error C7631: 'anonymous-namespace::x': variable with internal linkage declared but not defined

這類變數必須在所使用的相同轉譯單位中定義。 例如,您可以提供明確的初始化表達式或個別定義。

類型完整性和衍生對基底指標轉換

在 C++20 之前的 C++ 標準中,從衍生類別轉換成基類不需要衍生類別是完整的類別類型。 C++ 標準委員會已核准適用於所有 C++ 語言版本的追溯瑕疵報告變更。 這項變更會將轉換程式與類型特性對齊,例如 std::is_base_of,這確實需要衍生類別是完整的類別類型。

以下是範例:

template<typename A, typename B>
struct check_derived_from
{
    static A a;
    static constexpr B* p = &a;
};

struct W { };
struct X { };
struct Y { };

// With this change this code will fail as Z1 is not a complete class type
struct Z1 : X, check_derived_from<Z1, X>
{
};

// This code failed before and it will still fail after this change
struct Z2 : check_derived_from<Z2, Y>, Y
{
};

// With this change this code will fail as Z3 is not a complete class type
struct Z3 : W
{
    check_derived_from<Z3, W> cdf;
};

此行為變更適用於 MSVC 的所有 C++ 語言模式,而不只是 /std:c++20/std:c++latest

縮小轉換會更一致地診斷

MSVC 會在大括號清單初始化表達式中發出縮小轉換的警告。 先前,編譯程式不會診斷從較大的 enum 基礎型別到較窄整數型別的縮小轉換。 (編譯程序錯誤地將其視為整數升階,而不是轉換)。 如果刻意進行縮小轉換,您可以在初始化表達式自變數上使用 來避免警告 static_cast 。 或者,選擇較大的目的地整數類型。

以下是使用明確 static_cast 解決警告的範例:

enum E : long long { e1 };
struct S { int i; };

void f(E e) {
    S s = { e }; // warning: conversion from 'E' to 'int' requires a narrowing conversion
    S s1 = { static_cast<int>(e) }; // Suppress warning with explicit conversion
}

Visual Studio 2019 16.8 版中的一致性改善

'Class rvalue used as lvalue' extension

MSVC 有一個延伸模組,允許使用類別右值做為左值。 延伸模組不會延長類別右值存留期,而且可能會導致運行時間未定義的行為。 我們現在會強制執行標準規則,並不允許在下 /permissive-使用此延伸模組。 如果您還無法使用 /permissive- ,您可以使用 /we4238 明確不允許擴充功能。 以下是範例:

// Compiling with /permissive- now gives:
// error C2102: '&' requires l-value
struct S {};

S f();

void g()
{
    auto p1 = &(f()); // The temporary returned by 'f' is destructed after this statement. So 'p1' points to an invalid object.

    const auto &r = f(); // This extends the lifetime of the temporary returned by 'f'
    auto p2 = &r; // 'p2' points to a valid object
}

「非命名空間範圍中的明確特製化」延伸模組

MSVC 有一個延伸模組,允許在非命名空間範圍中明確特製化。 它現在是標準的一部分,在CWG 727的解決之後。 不過,有行為差異。 我們已調整編譯程序的行為,以符合標準。

// Compiling with 'cl a.cpp b.cpp /permissive-' now gives:
//   error LNK2005: "public: void __thiscall S::f<int>(int)" (??$f@H@S@@QAEXH@Z) already defined in a.obj
// To fix the linker error,
// 1. Mark the explicit specialization with 'inline' explicitly. Or,
// 2. Move its definition to a source file.

// common.h
struct S {
    template<typename T> void f(T);
    template<> void f(int);
};

// This explicit specialization is implicitly inline in the default mode.
template<> void S::f(int) {}

// a.cpp
#include "common.h"

int main() {}

// b.cpp
#include "common.h"

檢查抽象類類型

C++20 Standard 已變更進程編譯程式,以偵測抽象類類型作為函式參數的使用。 具體而言,它不再是 SFINAE 錯誤。 先前,如果編譯程式偵測到函式範本的特製化會有抽象類類型實例做為函式參數,則該特製化會被視為格式不正確。 它不會新增至一組可行的候選函式。 在 C++20 中,在呼叫 函式之前,不會檢查抽象類類型的參數。 效果是,用來編譯的程序代碼不會造成錯誤。 以下是範例:

class Node {
public:
    int index() const;
};

class String : public Node {
public:
    virtual int size() const = 0;
};

class Identifier : public Node {
public:
    const String& string() const;
};

template<typename T>
int compare(T x, T y)
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

int compare(const Node& x, const Node& y)
{
    return compare(x.index(), y.index());
}

int f(const Identifier& x, const String& y)
{
    return compare(x.string(), y);
}

先前,對的呼叫compare會嘗試使用 StringT樣板自變數來特製化函式範本compare。 因為 是抽象類, String 所以無法產生有效的特製化。 唯一可行的候選取項目是 compare(const Node&, const Node&)。 不過,在 C++20 下,在呼叫 函式之前,不會檢查抽象類類型。 因此,特製化compare(String, String)會新增至一組可行的候選專案,而且它被選擇為最佳候選專案,因為 從轉換成 const String&String 比轉換const String&const Node&的轉換順序更好。

在 C++20 下,此範例的其中一個可能修正是使用概念;也就是說,將的定義 compare 變更為:

template<typename T>
int compare(T x, T y) requires !std::is_abstract_v<T>
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

或者,如果 C++ 概念無法使用,您可以回復為 SFINAE:

template<typename T, std::enable_if_t<!std::is_abstract_v<T>, int> = 0>
int compare(T x, T y)
{
    return x < y ? -1 : (x > y ? 1 : 0);
}

支援P0960R3 - 允許從括弧化值清單中初始化匯總

C++20 P0960R3 新增使用括弧初始化表達式清單初始化匯總的支援。 例如,下列程式代碼在 C++20 中有效:

struct S {
    int i;
    int j;
};

S s(1, 2);

這項功能大部分都是加總的,也就是說,程式代碼現在會編譯之前未編譯的程序代碼。 不過,它確實會變更 的行為 std::is_constructible。 在 C++17 模式中,這會 static_assert 失敗,但在 C++20 模式中,它會成功:

static_assert(std::is_constructible_v<S, int, int>, "Assertion failed!");

如果您使用此類型特性來控制多載解析,可能會導致 C++17 與 C++20 之間的行為變更。

涉及函式範本的多載解析

先前,編譯程式允許某些程式代碼在不應該編譯的情況下 /permissive- 進行編譯。 效果是,編譯程式稱為錯誤函式,導致運行時間行為發生變更:

int f(int);

namespace N
{
    using ::f;
    template<typename T>
    T f(T);
}

template<typename T>
void g(T&& t)
{
}

void h()
{
    using namespace N;
    g(f);
}

的呼叫 g 會使用包含兩個函 ::f 式和 N::f的多載集。 因為 N::f 是函式範本,編譯程式應該將函式自變數 視為非推斷的內容。 這表示,在此情況下,對的呼叫 g 應該會失敗,因為編譯程式無法推斷樣板參數 T的類型。 不幸的是,編譯程式並未捨棄它已經決定 ::f 與函式呼叫相符的事實。 編譯程式不會發出錯誤,而是會產生使用 ::f 做為 自變數呼叫g的程序代碼。

假設在許多情況下,使用 ::f 做為函式自變數是使用者預期的結果,只有在使用 /permissive-編譯程式代碼時才會發出錯誤。

/await 移轉至 C++20 協同程式

標準 C++20 協同程式現在預設會在 和 /std:c++latest/std:c++20開啟。 它們與 Coroutines TS 和 選項下的 /await 支援不同。 從 /await 移轉至標準協同程式可能需要一些來源變更。

非標準關鍵詞

C++20 模式不支援舊的 awaityield 關鍵詞。 程式代碼必須改用 co_awaitco_yield 。 標準模式也不允許在協同程式中使用 return 。 協同 return 程式中的每個 都必須使用 co_return

// /await
task f_legacy() {
    ...
    await g();
    return n;
}
// /std:c++latest
task f() {
    ...
    co_await g();
    co_return n;
}

initial_suspend/final_suspend類型

在下 /await,承諾初始函式和暫止函式可宣告為傳 bool回 。 此行為並非標準。 在 C++20 中,這些函式必須傳回可等候的類別類型,通常是其中一個可等候的類型:std::suspend_always如果函式先前傳truefalse回 ,或std::suspend_never傳回 。

// /await
struct promise_type_legacy {
    bool initial_suspend() noexcept { return false; }
    bool final_suspend() noexcept { return true; }
    ...
};

// /std:c++latest
struct promise_type {
    auto initial_suspend() noexcept { return std::suspend_never{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
    ...
};

的類型 yield_value

在 C++20 中,promise yield_value 函式必須傳回可等候的類型。 在 /await 模式中,允許函 yield_value 式傳回 void,而且一律會暫停。 這類函式可以取代為傳回的 std::suspend_always函式。

// /await
struct promise_type_legacy {
    ...
    void yield_value(int x) { next = x; };
};

// /std:c++latest
struct promise_type {
    ...
    auto yield_value(int x) { next = x; return std::suspend_always{}; }
};

例外狀況處理函式

/await 支援沒有例外狀況處理函式或名為 且接受 set_exceptionstd::exception_ptr之例外狀況處理函式的承諾類型。 在 C++20 中,Promise 類型必須具有名為 unhandled_exception 的函式,而該函式不接受任何自變數。 如有需要,可以從 取得 std::current_exception 例外狀況物件。

// /await
struct promise_type_legacy {
    void set_exception(std::exception_ptr e) { saved_exception = e; }
    ...
};
// /std:c++latest
struct promise_type {
    void unhandled_exception() { saved_exception = std::current_exception(); }
    ...
};

不支援推斷的協同程式傳回類型

C++20 不支援包含佔位元型型別協同程式,例如 auto。 必須明確宣告協同程序傳回型別。 在下 /await,這些推斷型別一律牽涉到實驗型別,而且需要包含定義必要類型的標頭:其中一個 std::experimental::task<T>std::experimental::generator<T>std::experimental::async_stream<T>

// /await
auto my_generator() {
    ...
    co_yield next;
};

// /std:c++latest
#include <experimental/generator>
std::experimental::generator<int> my_generator() {
    ...
    co_yield next;
};

傳回 return_value 的型別

promise return_value 函式的傳回型別必須是 void。 在 /await 模式中,傳回類型可以是任何專案,而且會被忽略。 此診斷可協助偵測細微的錯誤,例如當作者誤認為的傳回值 return_value 傳回給呼叫端時。

// /await
struct promise_type_legacy {
    ...
    int return_value(int x) { return x; } // incorrect, the return value of this function is unused and the value is lost.
};

// /std:c++latest
struct promise_type {
    ...
    void return_value(int x) { value = x; }; // save return value
};

傳回物件轉換行為

如果協同程式宣告的傳回型別不符合 Promise get_return_object 函式的傳回型別,則從 get_return_object 傳回的物件會轉換成協同程式的傳回型別。 在下 /await,這項轉換會提前完成,然後協同程序主體才有機會執行。 在 或 /std:c++latest/std:c++20,當值傳回給呼叫端時,就會完成此轉換。 它允許未在初始暫止點暫停的協同程式,以使用協同程序主體內所 get_return_object 傳回的物件。

協同程式承諾參數

在 C++20 中,編譯程式會嘗試將協同程序參數 (如果有的話)傳遞至 Promise 類型的建構函式。 如果失敗,它會使用預設建構函式重試。 在 /await 模式中,只會使用預設建構函式。 如果承諾有多個建構函式,這項變更可能會導致行為差異。 或者,如果有從協同程序參數轉換成 Promise 類型的轉換。

struct coro {
    struct promise_type {
        promise_type() { ... }
        promise_type(int x) { ... }
        ...
    };
};

coro f1(int x);

// Under /await the promise gets constructed using the default constructor.
// Under /std:c++latest the promise gets constructed using the 1-argument constructor.
f1(0);

struct Object {
template <typename T> operator T() { ... } // Converts to anything!
};

coro f2(Object o);

// Under /await the promise gets constructed using the default constructor
// Under /std:c++latest the promise gets copy- or move-constructed from the result of
// Object::operator coro::promise_type().
f2(Object{});

/permissive- 和 C++20 模組預設位於 下 /std:c++20

在 和 /std:c++latest/std:c++20,預設會開啟 C++20 模組支援。 如需這項變更的詳細資訊,以及和 import 有條件地視為關鍵詞的案例module,請參閱 Visual Studio 2019 16.8 版中 MSVC 的標準 C++20 模組支援。

作為模組支援的必要條件,permissive-現在會在或/std:c++latest指定時/std:c++20啟用。 如需詳細資訊,請參閱/permissive-

針對先前在 和下 /std:c++latest 編譯的程式代碼需要不符合規範的編譯程序行為, /permissive 可以指定來關閉編譯程式中的嚴格一致性模式。 編譯程式選項必須出現在 /std:c++latest 命令行自變數清單中。 不過, /permissive 如果偵測到模組使用量,則會產生錯誤:

錯誤 C1214:模組與透過 'option' 要求的非標準行為衝突

選項最常見的值為

選項 描述
/Zc:twoPhase- C++20 模組需要兩階段名稱查閱,並由 隱含 /permissive-
/Zc:hiddenFriend- C++20 模組需要標準隱藏的朋友名稱查閱規則,並由 隱含 /permissive-
/Zc:lambda- C++20 模組需要標準 Lambda 處理,且由 /std:c++20 模式或更新版本隱含。
/Zc:preprocessor- C++20 標頭單位使用量和建立只需要符合規範的預處理器。 具名模組不需要此選項。

/experimental:module使用隨附於Visual Studio的模組時,仍需要std.*選項,因為它們尚未標準化。

選項 /experimental:module 也表示 /Zc:twoPhase/Zc:lambda/Zc:hiddenFriend。 先前,使用模組編譯的程式代碼有時可能會 /Zc:twoPhase- 使用 編譯模組。 不再支援此行為。

Visual Studio 2019 16.9 版的一致性改善

參考直接初始化中暫存的複製初始化

核心工作組問題 CWG 2267 處理括弧初始化表達式清單與括號初始化表達式清單之間的不一致。 決議統一了兩種形式。

Visual Studio 2019 16.9 版在所有編譯程式模式中 /std 實作已變更的行為。 不過,因為可能是來源中斷性變更,所以只有在使用 /permissive-編譯程序代碼時才支援。

此範例示範行為變更:

struct A { };

struct B {
    explicit B(const A&);
};

void f()
{
    A a;
    const B& b1(a);     // Always an error
    const B& b2{ a };   // Allowed before resolution to CWG 2267 was adopted: now an error
}

解構函式特性和可能建構的子物件

核心工作組問題 CWG 2336 涵蓋具有虛擬基類之類別中解構函式隱含例外狀況規格的遺漏。 遺漏表示衍生類別中的解構函式,如果基底是抽象且具有 virtual 基底,則例外狀況規格可能會比基類弱。

Visual Studio 2019 16.9 版在所有編譯程式模式中 /std 實作已變更的行為。

此範例示範解譯如何變更:

class V {
public:
    virtual ~V() noexcept(false);
};

class B : virtual V {
    virtual void foo () = 0;
    // BEFORE: implicitly defined virtual ~B() noexcept(true);
    // AFTER: implicitly defined virtual ~B() noexcept(false);
};

class D : B {
    virtual void foo ();
    // implicitly defined virtual ~D () noexcept(false);
};

在此變更之前,隱含定義的解構函式 Bnoexcept,因為只會考慮可能建構的子物件。 而且,基類 V 不是可能建構的子對象,因為它是基 virtual 底,而且 B 是抽象的。 不過,基類 V 是類別的潛在建構子物件 D,因此 D::~D 判斷 noexcept(false)為 ,導致衍生類別的例外狀況規格比其基底較弱。 此解譯不安全。 如果例外狀況從衍生自 B 的類別解構函式擲回,則可能會導致不正確的運行時間行為。

透過這項變更,如果解構函式具有虛擬解構函式,而且任何虛擬基類都有可能擲回解構函式,則解構函式也會擲回。

類似的類型和參考系結

核心工作組問題 CWG 2352 處理參考系結規則與類型相似性變更之間的不一致。 先前的瑕疵報告(如 CWG 330)導入了不一致的情況。 這會影響 Visual Studio 2019 16.0 到 16.8 版。

透過這項變更,從 Visual Studio 2019 16.9 版開始,先前系結 Visual Studio 2019 16.0 版至 16.8 版暫存的程式代碼,現在只要涉及的類型與 cv 限定符不同,就可以直接系結。

Visual Studio 2019 16.9 版在所有編譯程式模式中 /std 實作已變更的行為。 可能是來源中斷性變更。

如需相關變更,請參閱 具有不相符 cv 限定符 的類型參考。

此範例顯示已變更的行為:

int *ptr;
const int *const &f() {
    return ptr; // Now returns a reference to 'ptr' directly.
    // Previously returned a reference to a temporary and emitted C4172
}

更新可能會變更依賴所引進暫時性的程序行為:

int func() {
    int i1 = 13;
    int i2 = 23;
    
    int* iptr = &i1;
    int const * const&  iptrcref = iptr;

    // iptrcref is a reference to a pointer to i1 with value 13.
    if (*iptrcref != 13)
    {
        return 1;
    }
    
    // Now change what iptr points to.

    // Prior to CWG 2352 iptrcref should be bound to a temporary and still points to the value 13.
    // After CWG 2352 it is bound directly to iptr and now points to the value 23.
    iptr = &i2;
    if (*iptrcref != 23)
    {
        return 1;
    }

    return 0;
}

/Zc:twoPhase/Zc:twoPhase- 選項行為變更

一般而言,MSVC 編譯程式選項會以最後一個看到獲勝的原則運作。 不幸的是,這不是和/Zc:twoPhase-選項的情況/Zc:twoPhase。 這些選項是「粘性」,因此稍後的選項無法覆寫它們。 例如:

cl /Zc:twoPhase /permissive a.cpp

在此情況下,第一 /Zc:twoPhase 個選項會啟用嚴格的雙階段名稱查閱。 第二個選項旨在停用嚴格的一致性模式(與 相反), /permissive-但並未停用 /Zc:twoPhase

Visual Studio 2019 16.9 版在所有編譯程式模式中 /std 變更此行為。 /Zc:twoPhase/Zc:twoPhase- 不再是「粘性」,而稍後的選項可以覆寫它們。

解構函式範本上的明確 noexcept-specifiers

編譯程式先前已接受以非擲回例外狀況規格宣告的解構函式範本,但未明確定義 noexcept-specifier。 解構函式的隱含例外狀況規格取決於 類別的屬性 - 在範本定義點可能不知道的屬性。 C++ 標準也需要此行為:如果解構函式在沒有 noexcept-specifier 的情況下宣告,則會有隱含的例外狀況規格,而且函式的其他宣告可能沒有 noexcept-specifier。

Visual Studio 2019 16.9 版會變更為所有 /std 編譯程式模式中一致的行為。

此範例顯示編譯程式行為的變更:

template <typename T>
class B {
    virtual ~B() noexcept; // or throw()
};

template <typename T>
B<T>::~B() { /* ... */ } // Before: no diagnostic.
// Now diagnoses a definition mismatch. To fix, define the implementation by 
// using the same noexcept-specifier. For example,
// B<T>::~B() noexcept { /* ... */ }

C++20 中的重寫表達式

自 Visual Studio 2019 16.2 版起,在 底下 /std:c++latest,編譯程式已接受類似下列範例的程式代碼:

#include <compare>

struct S {
    auto operator<=>(const S&) const = default;
    operator bool() const;
};

bool f(S a, S b) {
    return a < b;
}

不過,編譯程式不會叫用作者可能預期的比較函式。 此程式碼應該已重寫 a < b(a <=> b) < 0。 相反地,編譯程式會使用 operator bool() 使用者定義的轉換函式,並比較 bool(a) < bool(b)。 在 Visual Studio 2019 16.9 版和更新版本中,編譯程式會使用預期的宇宙飛船運算符運算式來重寫表達式。

原始檔中斷性變更

正確套用轉換至重寫表達式有另一個效果:編譯程式也會正確診斷嘗試重寫表達式的模棱兩可。 請考慮此範例:

struct Base {
    bool operator==(const Base&) const;
};

struct Derived : Base {
    Derived();
    Derived(const Base&);
    bool operator==(const Derived& rhs) const;
};

bool b = Base{} == Derived{};

在 C++17 中,由於表達式右側的 衍生到基底轉換 Derived ,因此會接受此程序代碼。 在 C++20 中,也會新增合成運算式候選專案: Derived{} == Base{}。 由於標準中的規則會根據轉換來贏得函式,因此,和之間的Base::operator==Derived::operator==選擇是無法決定的。 因為兩個表達式中的轉換序列彼此沒有更好或更差,因此範例程式代碼會產生模棱兩可的情況。

若要解決模棱兩可的問題,請新增不會受限於兩個轉換序列的新候選專案:

bool operator==(const Derived&, const Base&);

運行時間中斷性變更

由於運算符在 C++20 中重寫規則,因此多載解析可能會尋找在較低語言模式中找不到的新候選專案。 而且,新候選人可能比年長的候選人更好。 請考慮此範例:

struct iterator;
struct const_iterator {
  const_iterator(const iterator&);
  bool operator==(const const_iterator &ci) const;
};

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == *this; }
};

在 C++17 中,唯一的候選專案 ci == *thisconst_iterator::operator==。 這是相符項目,因為 *this 會經歷衍生到基底轉換至 const_iterator。 在 C++20 中,會新增另一個重寫候選專案: *this == ci,它會叫用 iterator::operator==。 此候選專案不需要轉換,因此比 比 更好 const_iterator::operator==。 新候選項目的問題在於它是目前正在定義的函式,因此函式的新語意會造成 無限遞歸的定義 iterator::operator==

為了協助範例之類的程式代碼,編譯程式會實作新的警告:

$ cl /std:c++latest /c t.cpp
t.cpp
t.cpp(8): warning C5232: in C++20 this comparison calls 'bool iterator::operator ==(const const_iterator &) const' recursively

若要修正程式代碼,請明確說明要使用哪一個轉換:

struct iterator {
  bool operator==(const const_iterator &ci) const { return ci == static_cast<const const_iterator&>(*this); }
};

Visual Studio 2019 16.10 版的一致性改善

為類別複製初始化選擇錯誤的多載

假設此範例程式代碼:

struct A { template <typename T> A(T&&); };
struct B { operator A(); };
struct C : public B{};
void f(A);
f(C{});

舊版編譯程式會使用 的樣板化轉換建構函式A,錯誤地將 的 f 自變數從 型CA別轉換成 。 標準 C++ 需要改用轉換運算子 B::operator A 。 在 Visual Studio 2019 16.10 版和更新版本中,多載解析行為會變更為使用正確的多載。

這項變更也可以在某些其他情況下更正所選的多載:

struct Base 
{
    operator char *();
};

struct Derived : public Base
{
    operator bool() const;
};

void f(Derived &d)
{
    // Implicit conversion to bool previously used Derived::operator bool(), now uses Base::operator char*.
    // The Base function is preferred because operator bool() is declared 'const' and requires a qualification
    // adjustment for the implicit object parameter, while the Base function does not.
    if (d)
    {
        // ...
    }
}

浮點常值的剖析不正確

在 Visual Studio 2019 16.10 版和更新版本中,浮點常值會根據其實際類型來剖析。 舊版編譯程式一律會剖析浮點常值,就好像它有類型 double 一樣,然後將結果轉換成實際類型。 此行為可能會導致不正確的捨入和拒絕有效值:

// The binary representation is '0x15AE43FE' in VS2019 16.9
// The binary representation is '0x15AE43FD' in VS2019 16.10
// You can use 'static_cast<float>(7.038531E-26)' if you want the old behavior.
float f = 7.038531E-26f;

宣告點不正確

舊版編譯程式無法像下列範例一樣編譯自我引用程序代碼:

struct S {
    S(int, const S*);

    int value() const;
};

S s(4, &s);

編譯程式在剖析整個宣告之前不會宣告變數 s ,包括建構函式自變數。 建構函式自變數清單中的 查閱 s 將會失敗。 在 Visual Studio 2019 16.10 版和更新版本中,此範例現在會正確編譯。

不幸的是,這項變更可能會中斷現有的程序代碼,如下列範例所示:

S s(1, nullptr); // outer s
// ...
{
   S s(s.value(), nullptr); // inner s
}

在舊版編譯程式中,當它在 建構函式自變數中查閱 s 的 「inner」 宣告時,它會尋找先前的宣告 s(“outer” s) 和程式代碼編譯。 從 16.10 版開始,編譯程式會改為發出警告 C4700 。 這是因為編譯程式現在會在剖析建構函式自變數之前宣告 「inner」。 s 因此,查閱會 s 尋找尚未初始化的「內部」 s

類別範本的明確特製化成員

舊版編譯程式錯誤地標示了類別範本成員的明確特製化,就好像在主要範本中也定義一樣 inline 。 此行為表示編譯程式有時會拒絕符合規範的程序代碼。 在 Visual Studio 2019 16.10 版和更新版本中,明確特製化不再以隱含方式標示為inline/permissive-模式。 請考慮此範例:

原始程式檔 s.h

// s.h
template<typename T>
struct S {
    int f() { return 1; }
};
template<> int S<int>::f() { return 2; }

原始程式檔 s.cpp

// s.cpp
#include "s.h"

原始程式檔 main.cpp

// main.cpp
#include "s.h"

int main()
{
}

若要解決上述範例中的連結器錯誤,請明確新增 inlineS<int>::f

template<> inline int S<int>::f() { return 2; }

推斷的傳回型別名稱管理

在 Visual Studio 2019 16.10 版和更新版本中,編譯程式會變更它如何產生已推斷傳回型別的函式名稱。 例如,請考慮下列函式:

auto f() { return 0; }
auto g() { []{}; return 0; }

舊版編譯程式會產生連結器的這些名稱:

f: ?f@@YAHXZ -> int __cdecl f(void)
g: ?g@@YA@XZ -> __cdecl g(void)

令人驚訝的是,由於函式主體中本機 Lambda 所造成的其他語意行為,會省略 g 傳回型別。 這種不一致使得難以實作具有推斷傳回類型的導出函式:模組介面需要如何編譯函式主體的相關信息。 它需要資訊,才能在匯入端產生可正確連結至定義的函式。

編譯程式現在省略推斷傳回型別函式的傳回型別。 此行為與其他主要實作一致。 函式範本有例外:此版本的編譯程式會針對具有推斷傳回類型的函式範本引進新的管理名稱行為:

template <typename T>
auto f(T) { return 1; }

template <typename T>
decltype(auto) g(T) { return 1.; }

int (*fp1)(int) = &f;
double (*fp2)(int) = &g;

decltype(auto) 的受管理名稱auto現在會出現在二進位檔中,而不是推斷的傳回類型:

f: ??$f@H@@YA?A_PH@Z -> auto __cdecl f<int>(int)
g: ??$g@H@@YA?A_TH@Z -> decltype(auto) __cdecl g<int>(int)

舊版編譯程式會包含推斷的傳回型別做為簽章的一部分。 當編譯程式在受管理的名稱中包含傳回類型時,可能會導致連結器問題。 有些其他格式良好的案例會變得模棱兩可的連結器。

新的編譯程式行為可能會產生二進位中斷性變更。 請考慮此範例:

原始程式檔 a.cpp

// a.cpp
auto f() { return 1; }

原始程式檔 main.cpp

// main.cpp
int f();
int main() { f(); }

在 16.10 版之前的版本中,編譯程式會產生看起來像int f()的名稱auto f(),即使其語意上不同函式也一樣。 這表示範例會編譯。 若要修正此問題,請勿依賴 auto 的原始定義 f。 請改為將寫入為 int f()。 由於已推斷傳回型別的函式一律會編譯,因此ABI影響會最小化。

忽略屬性的 nodiscard 警告

舊版編譯程式會以無訊息方式忽略屬性的特定用法 nodiscard 。 如果屬性位於未套用至所宣告之函式或類別的語法位置,則會忽略屬性。 例如:

static [[nodiscard]] int f() { return 1; }

在 Visual Studio 2019 16.10 版和更新版本中,編譯程式會改為發出層級 4 警告 C5240:

a.cpp(1): warning C5240: 'nodiscard': attribute is ignored in this syntactic position

若要修正此問題,請將 屬性移至正確的語法位置:

[[nodiscard]] static int f() { return 1; }

模組 include purview 中具有系統標頭名稱的指示詞警告

在 Visual Studio 2019 16.10 版和更新版本中,編譯程式會發出警告,以防止常見的模組介面撰寫錯誤。 如果您在語句之後 export module 包含標準連結庫標頭,編譯程式會發出警告 C5244。 以下是範例:

export module m;
#include <vector>

export
void f(std::vector<int>);

開發人員可能不想讓模組 m 擁有的內容 <vector>。 編譯程式現在發出警告,以協助尋找並修正問題:

m.ixx(2): warning C5244: '#include <vector>' in the purview of module 'm' appears erroneous. Consider moving that directive before the module declaration, or replace the textual inclusion with an "import <vector>;".
m.ixx(1): note: see module 'm' declaration

若要修正此問題,請在 之前export module m;移動 #include <vector>

#include <vector>
export module m;

export
void f(std::vector<int>);

未使用內部連結函式的警告

在 Visual Studio 2019 16.10 版和更新版本中,編譯程式會在更多情況下發出警告,其中已移除具有內部連結的未參考函式。 舊版編譯程式會針對下列程式代碼發出警告 C4505

static void f() // warning C4505: 'f': unreferenced function with internal linkage has been removed
{
}

編譯程式現在也會警告匿名命名空間中未參考的 auto 函式和未參考的函式。 它會針對下列兩個 函式發出預設 警告 C5245:

namespace
{
    void f1() // warning C5245: '`anonymous-namespace'::f1': unreferenced function with internal linkage has been removed
    {
    }
}

auto f2() // warning C5245: 'f2': unreferenced function with internal linkage has been removed
{
    return []{ return 13; };
}

大括弧 elision 上的警告

在 Visual Studio 2019 16.10 版和更新版本中,編譯程式會在未針對子物件使用大括弧的初始化清單上發出警告。 編譯程式會 發出預設 警告 C5246。

以下是範例:

struct S1 {
  int i, j;
};

struct S2 {
   S1 s1;
   int k;
};

S2 s2{ 1, 2, 3 }; // warning C5246: 'S2::s1': the initialization of a subobject should be wrapped in braces

若要修正此問題,請將子物件的初始化包裝在大括弧中:

S2 s2{ { 1, 2 }, 3 };

正確偵測 const 物件是否未初始化

在 Visual Studio 2019 16.10 版和更新版本中,當您嘗試定義 const 未完全初始化的物件時,編譯程式現在會發出錯誤 C2737:

struct S {
   int i;
   int j = 2;
};

const S s; // error C2737: 's': const object must be initialized

舊版編譯程式允許此程式代碼編譯,即使未初始化也一樣 S::i

若要修正此問題,請先初始化所有成員,再建立 const 對象的實例:

struct S {
   int i = 1;
   int j = 2;
};

Visual Studio 2019 16.11 版中的一致性改善

/std:c++20 編譯程式模式

在 Visual Studio 2019 16.11 版和更新版本中,編譯程式現在支援 /std:c++20 編譯程式模式。 先前,C++20 功能只能在 Visual Studio 2019 的模式中使用 /std:c++latest 。 原本需要 /std:c++latest 模式的 C++20 功能現在可在 /std:c++20 最新版 Visual Studio 的模式或更新版本中運作。

另請參閱

Microsoft C/C++ 語言一致性