83

Giả sử tôi có một loại và tôi muốn đặt hàm tạo mặc định của nó là riêng tư. Tôi viết như sau:Khi nào một nhà xây dựng tư nhân không phải là một nhà xây dựng tư nhân?

class C { 
    C() = default; 
}; 

int main() { 
    C c;   // error: C::C() is private within this context (g++) 
        // error: calling a private constructor of class 'C' (clang++) 
        // error C2248: 'C::C' cannot access private member declared in class 'C' (MSVC) 
    auto c2 = C(); // error: as above 
} 

Tuyệt vời.

Nhưng sau đó, các nhà xây dựng hóa ra không được riêng tư như tôi nghĩ rằng nó là:

class C { 
    C() = default; 
}; 

int main() { 
    C c{};   // OK on all compilers 
    auto c2 = C{}; // OK on all compilers 
}  

này đập vào mắt tôi là rất đáng ngạc nhiên, bất ngờ, và hành vi rõ ràng không mong muốn. Tại sao điều này OK?

+21

Không phải là 'C c {}; 'khởi tạo tổng hợp sao cho không có hàm tạo nào được gọi? – NathanOliver

+5

Điều gì @NathanOliver nói. Bạn không có một hàm tạo do người dùng cung cấp, vì vậy 'C' là tổng hợp. –

+5

@KerrekSB Đồng thời, tôi khá ngạc nhiên khi người dùng khai báo một ctor một cách rõ ràng không làm cho ctor do người dùng cung cấp. – Angew

Trả lời

56

Bí quyết là trong C++ 14 8.4.2/5 [dcl.fct.def.default]:

... Một chức năng là dùng cung cấp nếu nó là sử dụng tuyên bố và không được mặc định rõ ràng hoặc bị xóa trên lần khai báo đầu tiên. ...

Có nghĩa là constructor mặc định C 's thực sự là không dùng cung cấp, bởi vì nó đã được mặc định một cách rõ ràng về kê khai đầu tiên của mình. Như vậy, C không có nhà xây dựng người dùng cung cấp và do đó là một tổng hợp mỗi 8.5.1/1 [dcl.init.aggr]:

Một tổng là một mảng hoặc một lớp học (khoản 9) với không có nhà xây dựng do người dùng cung cấp (12.1), không có cá nhân hoặc thành viên dữ liệu không được bảo vệ (Điều 11), không có lớp cơ sở (Điều 10) và không có chức năng ảo (10.3).

+12

Trong thực tế, một lỗi tiêu chuẩn nhỏ: thực tế là ctor mặc định là riêng tư có hiệu lực bỏ qua trong bối cảnh này. – Yakk

+2

@Yakk Tôi không cảm thấy đủ điều kiện để đánh giá điều đó. Các từ ngữ về ctor không được người dùng cung cấp trông rất thận trọng, mặc dù. – Angew

+1

@Yakk: Vâng, vâng và không. Nếu lớp học có bất kỳ thành viên dữ liệu nào, bạn sẽ có cơ hội để làm cho những người đó trở thành riêng tư. Nếu không có thành viên dữ liệu, có rất ít tình huống mà tình trạng này sẽ ảnh hưởng nghiêm trọng đến bất kỳ ai. –

49

Bạn không gọi hàm tạo mặc định, bạn đang sử dụng khởi tạo tổng hợp trên loại tổng hợp. loại tổng hợp được phép có một constructor defaulted, miễn là nó mặc định mà nó đầu tiên tuyên bố:

Từ [dcl.init.aggr]/1:

Một tổng hợp là một mảng hoặc một lớp học (khoản [lớp]) với

  • không constructors dùng cung cấp ([class.ctor]) (bao gồm cả những người thừa hưởng ([namespace.udecl]) từ một lớp cơ sở),
  • không tư nhân hoặc bảo vệ không tĩnh thành viên dữ liệu (khoản [lớp. truy cập]),
  • không có chức năng ảo ([class.virtual]) và
  • không có lớp cơ sở ảo, riêng tư hoặc được bảo vệ ([class.mi]).

và từ [dcl.fct.def.default]/5

chức năng Rõ ràng-defaulted và chức năng ngầm-tuyên bố được gọi chung là các chức năng defaulted, và việc thực hiện quy định nghĩa tiềm ẩn đối với họ ([class.ctor] [lớp. dtor], [class.copy]), có nghĩa là định nghĩa chúng là đã bị xóa. Một chức năng được người dùng cung cấp nếu nó được người dùng khai báo và không được mặc định rõ ràng hoặc bị xóa trên khai báo đầu tiên của nó. Một hàm do người dùng cung cấp mặc định rõ ràng (tức là, được mặc định rõ ràng sau lần khai báo đầu tiên) được xác định tại thời điểm nó được mặc định rõ ràng; nếu một hàm như vậy được định nghĩa ngầm định là đã bị xóa, thì chương trình đó không đúng định dạng. [Lưu ý: Khai báo một hàm như mặc định sau khi khai báo đầu tiên của nó có thể cung cấp hiệu quả thực hiện và định nghĩa ngắn gọn trong khi cho phép một giao diện nhị phân ổn định cho một cơ sở mã phát triển. - cuối note]

Do đó, yêu cầu của chúng tôi cho một tổng hợp bao gồm:

  • không có thành viên ngoài công lập
  • không có chức năng ảo
  • không lớp cơ sở ảo hay ngoài công lập
  • không có nhà cung cấp do người dùng cung cấp kế thừa hoặc cách khác, chỉ cho phép các nhà thầu là:
    • được khai báo hoàn toàn hoặc
    • tuyên bố rõ ràng và được xác định là mặc định cùng một lúc.

C đáp ứng tất cả các yêu cầu này.

Đương nhiên, bạn có thể loại bỏ các hành vi xây dựng mặc định sai lầm này bằng cách đơn giản cung cấp một constructor mặc định rỗng, hoặc bằng cách định nghĩa constructor mặc định sau khi tuyên bố nó:

class C { 
    C(){} 
}; 
// --or-- 
class C { 
    C(); 
}; 
inline C::C() = default; 
+2

Tôi thích câu trả lời này tốt hơn một chút so với Angew, nhưng tôi nghĩ nó sẽ được hưởng lợi từ một bản tóm tắt khi bắt đầu bằng tối đa hai câu. – PJTraill

Các vấn đề liên quan