Khi nào thì một phương thức khởi tạo riêng không phải là một phương thức khởi tạo riêng?


88

Giả sử tôi có một kiểu 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:

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 quá.

Nhưng sau đó, hàm tạo hóa ra không riêng tư như tôi nghĩ:

class C {
    C() = default;
};

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

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


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

5
@NathanOliver đã nói gì. Bạn không có hàm tạo do người dùng cung cấp, Ctổng hợp cũng vậy.
Kerrek SB

5
@KerrekSB Đồng thời, tôi khá ngạc nhiên khi người dùng khai báo rõ ràng một ctor không làm cho ctor đó do người dùng cung cấp.
Angew không còn tự hào về SO

1
@Angew Đó là lý do tại sao tất cả chúng ta đều ở đây :)
Barry

2
@Angew Nếu đó là một =defaultctor công khai , điều đó có vẻ hợp lý hơn. Nhưng =defaultctor riêng có vẻ như là một thứ quan trọng không thể bỏ qua. Hơn nữa, class C { C(); } inline C::C()=default;khá khác biệt là một điều đáng ngạc nhiên.
Yakk - Adam Nevraumont

Câu trả lời:


58

Thủ thuật là trong C ++ 14 8.4.2 / 5 [dcl.fct.def.default]:

... Một hàm do người dùng cung cấp nếu nó do người dùng khai báo và không được mặc định rõ ràng hoặc bị xóa trong lần khai báo đầu tiên. ...

Điều đó có nghĩa là hàm tạo Cmặc định của nó thực sự không do người dùng cung cấp, vì nó đã được mặc định rõ ràng trong lần khai báo đầu tiên. Do đó, Ckhông có hàm tạo do người dùng cung cấp và do đó là tổng trên 8.5.1 / 1 [dcl.init.aggr]:

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


13
Trên thực tế, một khiếm khuyết tiêu chuẩn nhỏ: thực tế là ctor mặc định là riêng tư trên thực tế bị bỏ qua trong ngữ cảnh này.
Yakk - Adam Nevraumont

2
@Yakk Tôi cảm thấy không đủ tư cách để đánh giá điều đó. Tuy nhiên, cách diễn đạt về ctor không do người dùng cung cấp có vẻ rất cân nhắc.
Angew không còn tự hào về nữa.

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

2
@KerrekSB Điều quan trọng nếu bạn đang cố gắng sử dụng lớp một loại "mã thông báo truy cập", kiểm soát ví dụ: ai có thể gọi một hàm dựa trên ai có thể tạo một đối tượng của lớp.
Angew không còn tự hào về SO

5
@Yakk Thú vị hơn nữa là nó C{}hoạt động ngay cả khi hàm tạo là deleted.
Barry

55

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 một kiểu tổng hợp. Các kiểu tổng hợp được phép có một phương thức khởi tạo mặc định, miễn là nó được mặc định ở nơi khai báo đầu tiên:

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

Tổng hợp là một mảng hoặc một lớp (Mệnh đề [class]) với

  • không có hàm tạo do người dùng cung cấp ([class.ctor]) (bao gồm cả những hàm được kế thừa ([namespace.udecl]) từ một lớp cơ sở),
  • không có thành viên dữ liệu tĩnh nào được bảo vệ hoặc riêng tư (Khoản [class.access]),
  • 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

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

Do đó, các yêu cầu của chúng tôi đối với tổng hợp là:

  • không có thành viên không công khai
  • không có chức năng ảo
  • không có lớp cơ sở ảo hoặc không công khai
  • không có hàm tạo do người dùng cung cấp được kế thừa hoặc cách khác, chỉ cho phép các hàm tạo:
    • được tuyên bố ngầm, hoặc
    • được khai báo rõ ràng và được định nghĩa 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ỏ hành vi xây dựng mặc định sai này bằng cách chỉ cần cung cấp một hàm tạo mặc định trống hoặc bằng cách xác định hàm tạo làm mặc định sau khi khai báo:

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

2
Tôi thích câu trả lời này có phần tốt hơn câu trả lời của Angew, nhưng tôi nghĩ rằng nó sẽ có lợi nếu tóm tắt ở đầu ít nhất là hai câu.
PJTraill

7

Angew củajaggedSpire của câu trả lời là tuyệt vời và áp dụng đối với. Và. Và.

Tuy nhiên, trong , mọi thứ thay đổi một chút và ví dụ trong OP sẽ không biên dịch nữa:

class C {
    C() = default;
};

C p;          // always error
auto q = C(); // always error
C r{};        // ok on C++11 thru C++17, error on C++20
auto s = C{}; // ok on C++11 thru C++17, error on C++20

Như đã chỉ ra bởi hai câu trả lời, lý do hai khai báo sau hoạt động là vì Clà một tập hợp và đây là sự khởi tạo tổng hợp. Tuy nhiên, theo kết quả của P1008 (sử dụng một ví dụ thúc đẩy không quá khác với OP), định nghĩa về tổng hợp thay đổi trong C ++ 20 thành, từ [dcl.init.aggr] / 1 :

Tổng hợp là một mảng hoặc một lớp ([class]) với

  • không có hàm tạo do người dùng khai báo hoặc kế thừa ([class.ctor]),
  • không có thành viên dữ liệu tĩnh không tĩnh trực tiếp hoặc riêng tư hoặc được bảo vệ ([class.access]),
  • 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]).

Nhấn mạnh của tôi. Bây giờ yêu cầu là không sử dụng tuyên bố nhà thầu, trong khi nó được sử dụng để được (như cả người dùng trích dẫn trong câu trả lời của họ và có thể xem lịch sử cho C ++ 11 , C ++ 14 , và C ++ 17 ) không dùng cung cấp nhà xây dựng . Hàm tạo mặc định cho Clà do người dùng khai báo, nhưng không do người dùng cung cấp và do đó không còn là tổng hợp trong C ++ 20.


Đây là một ví dụ minh họa khác về các thay đổi tổng hợp:

class A { protected: A() { }; };
struct B : A { B() = default; };
auto x = B{};

Bkhông phải là tổng hợp trong C ++ 11 hoặc C ++ 14 vì nó có một lớp cơ sở. Do đó, B{}chỉ cần gọi hàm tạo mặc định (do người dùng khai báo nhưng không do người dùng cung cấp), có quyền truy cập vào hàm tạo Amặc định được bảo vệ của.

Trong C ++ 17, là kết quả của P0017 , các tổng hợp được mở rộng để cho phép các lớp cơ sở. Blà một tổng hợp trong C ++ 17, có nghĩa B{}là khởi tạo tổng hợp phải khởi tạo tất cả các subobject - bao gồm cả Asubobject. Nhưng bởi vì hàm tạo Amặc định của được bảo vệ, chúng tôi không có quyền truy cập vào nó, do đó, quá trình khởi tạo này không được hình thành.

Trong C ++ 20, vì hàm tạo do Bngười dùng khai báo, nó một lần nữa không còn là một tập hợp, do đó, B{}trở lại gọi hàm tạo mặc định và điều này lại được khởi tạo đúng cách.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.