2016-12-02 139 views
22

我想結合使用C++ 11直接數據成員初始化和「using」語法來繼承基類的構造函數。現在使用gcc 5.4.0(在Ubuntu 16.04上),我觀察到一個奇怪的錯誤,如果數據成員類型沒有默認構造函數。它可能比較容易找上了以下最低例如,當明白:構造函數繼承和直接成員初始化

#include <iostream> 

struct Foo { 
    Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; } 
}; 

struct Base { 
    Base(int arg) { std::cout << "Base::Base(" << arg << ")" << std::endl; } 
}; 

struct Derived : public Base { 
    using Base::Base; 
    Foo foo{42}; 
}; 

int main() { 
    Derived derived{120}; 
} 

此代碼編譯並與鐺預期的行爲執行。用gcc編譯不能通過,因爲編譯器刪除構造Derived::Derived(int)

ttt.cpp: In function ‘int main()’: 
ttt.cpp:17:22: error: use of deleted function ‘Derived::Derived(int)’ 
    Derived derived{120}; 
        ^
ttt.cpp:12:15: note: ‘Derived::Derived(int)’ is implicitly deleted because the default definition would be ill-formed: 
    using Base::Base; 
      ^
ttt.cpp:12:15: error: no matching function for call to ‘Foo::Foo()’ 
ttt.cpp:4:3: note: candidate: Foo::Foo(int) 
    Foo(int arg) { std::cout << "Foo::Foo(" << arg << ")" << std::endl; } 
^
ttt.cpp:4:3: note: candidate expects 1 argument, 0 provided 
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(const Foo&) 
struct Foo { 
     ^
ttt.cpp:3:8: note: candidate expects 1 argument, 0 provided 
ttt.cpp:3:8: note: candidate: constexpr Foo::Foo(Foo&&) 
ttt.cpp:3:8: note: candidate expects 1 argument, 0 provided 

如果我添加一個默認的構造的Foo這樣的:

Foo() { std::cout << "Foo::Foo()" << std::endl; }; 

也可以GCC編譯它。代碼的行爲完全一樣,特別是Foo的默認構造函數永遠不會被執行。

所以我的問題是現在,這是有效的C + + 11?如果是的話,我可能已經在gcc中發現了一個bug。否則,gcc和clang都不應該給我一個錯誤消息,說明這是無效的C++ 11?

編輯問題後很好地回答@ vlad-from-moscow:這個bug似乎也出現在gcc 6.2中,所以我會提交一個bug報告。

2日編輯:已經有一個錯誤,我沒有在第一搜索發現:https://gcc.gnu.org/bugzilla/show_bug.cgi?id=67054

+0

我在cppreference.com等網頁上找不到任何關於此的內容。在那裏描述了「using」語法和括號初始化符,但是沒有提到關於兩者結合的任何內容。 –

回答

10

的GCC不滿足C++標準。 Derived類的繼承構造函數應該使用爲Derived繼承的構造函數指定的參數,在其mem-initializer列表中調用Base構造函數。

有被寫在C++標準(12.9繼承構造函數)

8一類的繼承的構造是隱含地,當它 是ODR使用的定義德音響(3.2)來創建它的類類型的對象(1.8)。定義繼承構造函數 隱德科幻執行組的類 初始化將由用戶編寫 直列構造該類與MEM-初始化列表,其 只有MEM-初始化進行有MEM-初始化-id,用於命名 類中使用聲明的嵌套名稱指定的類, 指定在以下的表達式列表,以及其函數體中的 複合語句爲空(12.6.2) 。如果該用戶編寫的構造函數不合格,則該程序爲 不合格。表達式列表中的每個表達式的格式爲 static_cast(p),其中p是相應的構造函數參數 的名稱,T是p的聲明類型。

也根據節(12.6。2初始化鹼和成員)

8在一個非委託構造,如果一個給定的非靜態數據成員 或基類不是由MEM-初始化-ID(包括 情況下指定其中存在沒有MEM-初始化列表因爲 具有noctor-初始化)和實體不是虛擬基類的 一個抽象類(10.4),然後

構造 - 如果實體是一個非靜態數據成員有一個 括號或等於初始值設定項,實體按照 8.5中的規定進行初始化;

+0

@VladfromMoscow:問題不在於Derived(120)'不會調用'Base(120)',更多的是它錯誤地假設表達式列表將是'foo()'(這是無效的),儘管提供了一個初始化器,實際的調用將是'foo(42)'。如果你刪除'foo'數據成員,那麼gcc接受代碼。 –

+0

@MatthieuM。我附上了我的答案。但是,我發現標準的各種版本之間存在矛盾。 –

5

它看起來像你說得對,有一個在GCC

的錯誤從§12.9[ class.inhctor]:

一個使用聲明(7.3.3)名稱構造隱式聲明一套繼承構造。從using聲明名爲類X繼承構造的 候選集包括實際 構造函數和名義構造,從默認的參數的轉換結果如下:

  • 所有非的X

模板的構造函數因此,這意味着你的Derived S級應該從其基地獲得一個構造函數,它接受一個int。按照類內成員初始化的正常規則,構建Derived的實例在Foo沒有默認構造函數的情況下應該不會成爲問題,因爲它沒有被使用。因此,有一個在GCC一個錯誤:

§13.3.1.3初始化通過構造[over.match.ctor]

當類類型的對象是直接初始化(8.5)[...],重載解析選擇構造函數。對於直接初始化,候選 函數是被初始化的對象的類的所有構造函數

所以構造函數Foo::Foo(int)應該被選中,這顯然不是在gcc中。


一個問題我讀這是後出現「這是否會爲Derived要刪除的默認構造函數?」答案是不。

便利地,所述標準提供了這個片段在下面的例子(我切除什麼不需要):

struct B1 { 
    B1(int); 
}; 

struct D1 : B1 { 
    using B1::B1; 
}; 

該組中D1本構造是[強調礦]

  • D1(),隱式聲明的默認構造函數,如果odr使用的話會形成錯誤
  • D1(const D1&),隱式聲明的拷貝構造函數,而不是繼承
  • D1(D1&&),隱式聲明的移動構造函數,而不是繼承
  • D1(int),隱式聲明的繼承構造
+2

這與Derived或Base的默認構造函數無關。 gcc需要Foo的默認構造函數,但它實際上從來不會調用它。 –

+0

@MartinHierholzer:你說過:「這段代碼用clang編譯並執行期望的行爲,使用gcc它不編譯,因爲編譯器刪除了Derived的構造函數:」。這是不正確的gcc行爲,以及我在這裏處理的內容。你不需要再做任何事情。 – AndyG

+1

參考你的編輯:這還不是Derived的默認構造函數是否被刪除。我對Derived的默認構造函數不感興趣,它從未使用過,gcc從未抱怨過沒有它。相反,gcc刪除想要的構造函數Derived :: Derived(int),因爲Foo的默認構造函數不存在! –