[英]Why shouldn't the inherited constructor inherit the default arguments?
C ++ Primer (第5版)第629頁指出:
- 如果基類構造函數具有默認參數,則不會繼承這些參數。 相反,派生類獲取多個繼承的構造函數,其中連續省略具有默認參數的每個參數。
這條規則背后的原因是什么?
鑒於目前的措辭; 我認為這些術語( C ++ WD n4527的第 12.9 / 1 節 )中有詳細說明(但主要是為了避免潛在的歧義);
繼承構造函數是一種類似於代碼生成的技術(“我想要我的基礎”)。 沒有辦法指定你得到的構造函數,你基本上都得到它們因此編譯器非常注意不要生成不明確的構造函數。
舉例來說;
#include <iostream>
using namespace std;
struct Base {
Base (int a = 0, int b = 1) { cout << "Base" << a << b << endl; }
};
struct Derived : Base {
// This would be ambiguous if the inherited constructor was Derived(int=0,int=1)
Derived(int c) { cout << "Derived" << c << endl; }
using Base::Base;
};
int main()
{
Derived d1(3);
Derived d2(4,5);
}
輸出;
Base01
Derived3
Base45
示例代碼 。
n4429 (如Jonathan Wakely所述)提出了關於繼承構造函數和類的使用聲明的措辭的更改。
鑒於該提案的意圖;
...這個提議使繼承構造函數就像繼承任何其他基類成員一樣。
有以下變化(新措辭);
更改7.3.3 namespace.udecl第15段:
當using聲明將基類的聲明帶入派生類時......這些隱藏或重寫的聲明被排除在using聲明引入的聲明集之外。
然后立即使用一個直接處理構造函數的示例(盡管沒有默認參數);
struct B1 {
B1(int);
};
struct B2 {
B2(int);
};
struct D1 : B1, B2 {
using B1::B1;
using B2::B2;
};
D1 d1(0); // ill-formed: ambiguous
struct D2 : B1, B2 {
using B1::B1;
using B2::B2;
D2(int); // OK: D2::D2(int) hides B1::B1(int) and B2::B2(int)
};
D2 d2(0); // calls D2::D2(int)
簡而言之,雖然可能不是最終的措辭,但似乎意圖是允許構造函數與其默認參數一起使用並明確排除隱藏和重寫的聲明,因此我相信會處理任何歧義。 措辭似乎確實簡化了標准,但在客戶端代碼中使用了相同的結果。
默認參數不是函數簽名的一部分,可以在以后添加,並且在受限范圍內添加,這將無法更改派生類的已定義構造函數,例如
// in A.h
struct A {
A(int, int);
};
// in B.h
#include "A.h"
struct B : A {
using A::A;
};
// in A.cc
#include "A.h"
A::A(int, int = 0) { }
在文件A.cc
您可以使用單個參數構造A
,因為默認參數是可見的,但是當聲明B
時,默認參數不可見,因此在繼承構造函數時不能考慮。 我相信這是默認參數得到特殊處理的一個原因。
雖然顯然繼承構造函數的工作方式可能會發生變化,但默認參數不會得到這種特殊處理,請參閱http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/n4429.html
這條規則背后的原因是什么?
它可以防止來自基類的默認參數的更改影響所有派生類(在派生類范圍內)的行為,這對派生類創建者來說是一個驚喜 。
正如Jonathan Wakely所提到的那樣, C ++ 17已經改變了這種行為 。 現在,參數列表中的默認參數將被繼承。
也就是說,如果我們在名為Base
的類中有以下構造函數,
struct Base {
Base(int a, int b, int c = 1, int d = 2, int e = 3) {}
};
然后對於上面的構造函數,這些是在C ++ 11 / C ++ 14中被“注入”派生類的相應構造函數:
struct Derived : Base {
using Base::Base;
/*
C++11/C++14:
Derived::Derived(int a, int b) : Base(a, b) {}
Derived::Derived(int a, int b, int c) : Base(a, b, c) {}
Derived::Derived(int a, int b, int c, int d) : Base(a, b, c, d) {}
Derived::Derived(int a, int b, int c, int d, int e) : Base(a, b, c, d, e) {}
*/
};
而C ++ 17中的那個現在要簡單得多:
struct Derived : Base {
using Base::Base;
/*
C++17:
Derived::Derived(int a, int b, int c = 1, int d = 2, int e = 3) : Base(a, b, c, d, e) {}
*/
};
基於繼承構造函數的cppreference.com頁面和引入更改的文章(P0136R1) ,指定了如何將繼承的構造函數拆分並“注入”到派生類中的整個[class.inhctor]\\1
子[class.inhctor]\\1
已被刪除。 (實際上整個[class.inhctor]
部分已被刪除) 。 然后用C ++ 17中的[namespace.udecl]\\16
中的一個簡單規則替換它(強調我的):
出於重載解析的目的 ,將using聲明引入派生類的函數視為派生類的成員。 特別是,隱式this參數應被視為指向派生類而不是基類的指針。 這對函數的類型沒有影響,並且在所有其他方面,函數仍然是基類的成員。 同樣, 在查找派生類(6.4.3.1)的構造函數或形成一組重載候選項 (16.3.1.3,16.3 )時,將使用聲明引入的構造函數視為派生類的構造函數 。 1.4,16.3.1.7)。 如果選擇這樣的構造函數來執行類類型對象的初始化,則隱式初始化構造函數所源自的基類以外的所有子對象(15.6.3)。
所以參數列表現在完全“延續”了。 實際上,這是我使用符合P0136R1的CLion和GCC 7.2的經驗,而我的非P0136R1兼容的Visual Studio 2017(15.6)顯示了較舊的4個構造函數,其默認參數被刪除。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.