簡體   English   中英

為什么 C++ 需要用戶提供的默認構造函數來默認構造一個 const 對象?

[英]Why does C++ require a user-provided default constructor to default-construct a const object?

C++ 標准(第 8.5 節)說:

如果程序要求對具有 const 限定類型 T 的對象進行默認初始化,則 T 應是具有用戶提供的默認構造函數的類類型。

為什么? 我想不出在這種情況下為什么需要用戶提供的構造函數的任何原因。

struct B{
  B():x(42){}
  int doSomeStuff() const{return x;}
  int x;
};

struct A{
  A(){}//other than "because the standard says so", why is this line required?

  B b;//not required for this example, just to illustrate
      //how this situation isn't totally useless
};

int main(){
  const A a;
}

原因是如果類沒有自定義構造函數,那么可以是POD,POD類默認是不初始化的。 那么如果你聲明一個未初始化的 POD 常量對象,它有什么用呢? 所以我認為標准強制執行這個規則,以便對象實際上是有用的。

struct POD
{
  int i;
};

POD p1; //uninitialized - but don't worry we can assign some value later on!
p1.i = 10; //assign some value later on!

POD p2 = POD(); //initialized

const POD p3 = POD(); //initialized 

const POD p4; //uninitialized  - error - as we cannot change it later on!

但是,如果您將類設為非 POD:

struct nonPOD_A
{
    nonPOD_A() {} //this makes non-POD
};

nonPOD_A a1; //initialized 
const nonPOD_A a2; //initialized 

請注意 POD 和非 POD 之間的區別。

用戶定義的構造函數是使類成為非 POD 的一種方法。 有幾種方法可以做到這一點。

struct nonPOD_B
{
    virtual void f() {} //virtual function make it non-POD
};

nonPOD_B b1; //initialized 
const nonPOD_B b2; //initialized 

注意 nonPOD_B 沒有定義用戶定義的構造函數。 編譯它。 它將編譯:

並注釋虛函數,然后按預期給出錯誤:


好吧,我想,你誤解了這段話。 它首先是這樣說的(第 8.5/9 節):

如果沒有為對象指定初始化程序,並且該對象是(可能是 cv 限定的)非 POD 類類型(或其數組),則該對象應默認初始化; [...]

它談論非 POD 類可能是 cv 限定的類型。 也就是說,如果沒有指定初始化程序,則非 POD 對象應默認初始化。 什么是默認初始化 對於非 POD,規范說 (§8.5/5),

默認初始化 T 類型的對象意味着:
— 如果 T 是非 POD 類類型(第 9 條),則調用 T 的默認構造函數(如果 T 沒有可訪問的默認構造函數,則初始化為病態);

它只是談論 T 的默認構造函數,無論是用戶定義的還是編譯器生成的都無關緊要。

如果您清楚這一點,那么請了解規范接下來說的是什么((第 8.5/9 節),

[...]; 如果對象是 const 限定類型,則基礎類類型應具有用戶聲明的默認構造函數。

所以這段文字暗示,如果對象是const 限定的POD 類型,並且沒有指定初始化程序(因為 POD 沒有默認初始化),則程序將是格式錯誤的:

POD p1; //uninitialized - can be useful - hence allowed
const POD p2; //uninitialized - never useful  - hence not allowed - error

順便說一下,這編譯得很好,因為它是非 POD,並且可以被默認初始化

純屬我的推測,但考慮到其他類型也有類似的限制:

int main()
{
    const int i; // invalid
}

因此,這條規則不僅是一致的,而且還(遞歸地)防止了統一的const (子)對象:

struct X {
    int j;
};
struct A {
    int i;
    X x;
}

int main()
{
    const A a; // a.i and a.x.j in unitialized states!
}

至於問題的另一方面(允許它用於具有默認構造函數的類型),我認為這個想法是具有用戶提供的默認構造函數的類型應該在構造后始終處於某種合理的狀態。 請注意,這些規則允許以下內容:

struct A {
    explicit
    A(int i): initialized(true), i(i) {} // valued constructor

    A(): initialized(false) {}

    bool initialized;
    int i;
};

const A a; // class invariant set up for the object
           // yet we didn't pay the cost of initializing a.i

那么也許我們可以制定一個規則,例如“必須在用戶提供的默認構造函數中合理地初始化至少一個成員”,但是為了防止墨菲而花費了太多時間。 C++ 傾向於在某些方面信任程序員。

這被認為是一個缺陷(針對所有版本的標准),並由核心工作組 (CWG) 缺陷 253 解決 http://eel.is/c++draft/dcl.init#7 中標准狀態的新措辭

如果 T 的默認初始化會調用用戶提供的 T 構造函數(不是從基類繼承的),或者如果

  • T 的每個直接非變體非靜態數據成員 M 都有一個默認成員初始值設定項,或者,如果 M 是類類型 X(或其數組),則 X 是 const-default-constructible,
  • 如果 T 是具有至少一個非靜態數據成員的聯合,則恰好有一個變體成員具有默認成員初始值設定項,
  • 如果 T 不是聯合,則對於每個具有至少一個非靜態數據成員(如果有)的匿名聯合成員,恰好一個非靜態數據成員具有默認成員初始值設定項,並且
  • T 的每個潛在構造的基類都是 const-default-constructible。

如果程序要求對具有常量限定的類型 T 的對象進行默認初始化,則 T 應為可常量默認構造的類類型或其數組。

這種措辭基本上意味着明顯的代碼有效。 如果你初始化所有的基和成員,你可以說A const a; 無論您如何或是否拼寫任何構造函數。

struct A {
};
A const a;

gcc 從 4.6.4 開始接受了這一點。 從 3.9.0 開始,clang 已經接受了這一點。 Visual Studio 也接受這一點(至少在 2017 年,不確定是否更早)。

我正在觀看 Timur Doumler 在 Meeting C++ 2018 上的演講,我終於意識到為什么標准在這里需要用戶提供的構造函數,而不僅僅是用戶聲明的構造函數。 它與值初始化的規則有關。

考慮兩個類: A有一個用戶聲明的構造函數, B有一個用戶提供的構造函數:

struct A {
    int x;
    A() = default;
};
struct B {
    int x;
    B() {}
};

乍一看,您可能認為這兩個構造函數的行為相同。 但是看看值初始化的行為如何不同,而只有默認初始化的行為相同:

  • A a; 是默認初始化:成員int x未初始化。
  • B b; 是默認初始化:成員int x未初始化。
  • A a{}; 是值初始化:成員int x零初始化的
  • B b{}; 是值初始化:成員int x未初始化。

現在看看當我們添加const時會發生什么:

  • const A a; 是默認初始化:由於問題中引用的規則,這是格式錯誤的
  • const B b; 是默認初始化:成員int x未初始化。
  • const A a{}; 是值初始化:成員int x零初始化的
  • const B b{}; 是值初始化:成員int x未初始化。

一個未初始化的const標量(例如int x成員)將是無用的:寫入它是格式錯誤的(因為它是const )並且從中讀取它是 UB (因為它持有一個不確定的值)。 因此,這條規則將阻止您創建這樣的事情,迫使你要么通過增加用戶提供的構造函數中添加初始化器選擇到危險的行為。

我認為當你有意不初始化一個對象時,有一個像[[uninitialized]]這樣的屬性來告訴編譯器會很好。 這樣我們就不會被迫讓我們的類不能簡單地默認構造來解決這個極端情況。 這個屬性實際上已經被提出,但就像所有其他標准屬性一樣,它不強制要求任何規范行為,只是對編譯器的一個提示。

恭喜,您已經發明了一種情況,在這種情況下,不需要為沒有初始化器的const聲明使用任何用戶定義的構造const

現在你能想出一個合理的規則重新措辭,涵蓋你的案件,但仍然使應該是非法的案件成為非法嗎? 是少於 5 段還是 6 段? 如何在任何情況下應用它是否簡單而明顯?

我認為想出一個規則來讓你創建的聲明有意義真的很難,而在閱讀代碼更難時,確保可以以對人們有意義的方式應用規則。 我更喜歡一個在大多數情況下是正確的有點限制性的規則,而不是一個難以理解和應用的非常微妙和復雜的規則。

問題是,是否有令人信服的理由讓規則更復雜? 是否有一些代碼在其他情況下很難編寫或理解,但如果規則更復雜,則可以更簡單地編寫?

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM