Cú pháp mới tập tin = mặc định trong C ++ 11


136

Tôi không hiểu tại sao tôi lại làm điều này:

struct S { 
    int a; 
    S(int aa) : a(aa) {} 
    S() = default; 
};

Tại sao không chỉ nói:

S() {} // instead of S() = default;

Tại sao mang lại một cú pháp mới cho điều đó?


30
Nitpick: defaultkhông phải là một từ khóa mới, nó chỉ là một cách sử dụng mới của một từ khóa đã dành riêng.


Mey be Câu hỏi này có thể giúp bạn.
FreeNickname

7
Ngoài các câu trả lời khác, tôi cũng cho rằng '= default;' là nhiều tài liệu tự.
Đánh dấu

Câu trả lời:


136

Hàm tạo mặc định mặc định được xác định cụ thể giống như hàm tạo mặc định do người dùng định nghĩa không có danh sách khởi tạo và câu lệnh ghép trống.

§12.1 / 6 [class.ctor] Một constructor mặc định được cài đặt sẵn và không được định nghĩa như xóa được mặc nhiên được định nghĩa khi nó là ODR được sử dụng để tạo ra một đối tượng của kiểu lớp của nó hoặc khi nó được mặc định một cách rõ ràng sau khi tuyên bố đầu tiên của mình. Hàm tạo mặc định được định nghĩa ngầm thực hiện tập hợp các khởi tạo của lớp sẽ được thực hiện bởi hàm tạo mặc định do người dùng viết cho lớp đó mà không có trình khởi tạo ctor (12.6.2) và câu lệnh ghép rỗng. [...]

Tuy nhiên, trong khi cả hai hàm tạo sẽ hành xử giống nhau, việc cung cấp một triển khai trống sẽ ảnh hưởng đến một số thuộc tính của lớp. Đưa ra một hàm tạo do người dùng định nghĩa, mặc dù nó không làm gì, làm cho kiểu không phải là tổng hợp và cũng không tầm thường . Nếu bạn muốn lớp của bạn là một loại tổng hợp hoặc một loại tầm thường (hoặc theo độ xuyên sáng, một loại POD), thì bạn cần phải sử dụng = default.

§8.5.1 / 1 [dcl.init.aggr] Tập hợp là một mảng hoặc một lớp không có hàm tạo do người dùng cung cấp, [và ...]

§12.1 / 5 [class.ctor] Một hàm tạo mặc định là tầm thường nếu nó không do người dùng cung cấp và [...]

§9 / 6 [class] Một lớp tầm thường là một lớp có hàm tạo mặc định tầm thường và [...]

Để lam sang tỏ:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() { };
};

int main() {
    static_assert(std::is_trivial<X>::value, "X should be trivial");
    static_assert(std::is_pod<X>::value, "X should be POD");
    
    static_assert(!std::is_trivial<Y>::value, "Y should not be trivial");
    static_assert(!std::is_pod<Y>::value, "Y should not be POD");
}

Ngoài ra, mặc định rõ ràng một hàm tạo sẽ tạo ra nó constexprnếu hàm tạo ngầm định đã và cũng sẽ cung cấp cho nó cùng một đặc tả ngoại lệ mà hàm tạo ẩn sẽ có. Trong trường hợp bạn đã đưa ra, hàm tạo ngầm định sẽ không có constexpr(vì nó sẽ để lại một thành viên dữ liệu chưa được khởi tạo) và nó cũng sẽ có một đặc tả ngoại lệ trống, do đó không có sự khác biệt. Nhưng có, trong trường hợp chung, bạn có thể chỉ định thủ công constexprvà đặc tả ngoại lệ để khớp với hàm tạo ẩn.

Việc sử dụng = defaultkhông mang lại sự đồng nhất, bởi vì nó cũng có thể được sử dụng với các hàm tạo và di chuyển sao chép / di chuyển. Ví dụ, một hàm tạo sao chép trống sẽ không thực hiện giống như một hàm tạo sao chép mặc định (sẽ thực hiện sao chép thành viên khôn ngoan của các thành viên của nó). Sử dụng cú pháp = default(hoặc = delete) thống nhất cho từng hàm thành viên đặc biệt này giúp mã của bạn dễ đọc hơn bằng cách nêu rõ ý định của bạn.


Hầu hết. 12.1 / 6: "Nếu hàm tạo mặc định do người dùng viết đó đáp ứng các yêu cầu của hàm constexprtạo (7.1.5), thì hàm tạo mặc định được xác định ngầm định là constexpr."
Casey

Trên thực tế, 8.4.2 / 2 có nhiều thông tin hơn: "Nếu một hàm được mặc định rõ ràng trong khai báo đầu tiên của nó, (a) nó được coi là hoàn toàn constexprnếu tuyên bố ngầm sẽ là, (b) nó được coi là có cùng một đặc tả ngoại lệ như thể nó đã được tuyên bố ngầm (15.4), ... "Nó không có sự khác biệt trong trường hợp cụ thể này, nhưng nói chung foo() = default;có một lợi thế nhỏ hơn foo() {}.
Casey

2
Bạn nói không có sự khác biệt, và sau đó tiếp tục giải thích sự khác biệt?

@hvd Trong trường hợp này không có sự khác biệt, bởi vì khai báo ngầm sẽ không constexpr(vì một thành viên dữ liệu không được khởi tạo lại) và đặc tả ngoại lệ của nó cho phép tất cả các ngoại lệ. Tôi sẽ làm cho nó rõ ràng hơn.
Joseph Mansfield

2
Cảm ơn bạn đã làm rõ. Tuy nhiên, dường như vẫn có một sự khác biệt với constexpr(mà bạn đã đề cập không nên tạo ra sự khác biệt ở đây): struct S1 { int m; S1() {} S1(int m) : m(m) {} }; struct S2 { int m; S2() = default; S2(int m) : m(m) {} }; constexpr S1 s1 {}; constexpr S2 s2 {};Chỉ s1đưa ra một lỗi, không phải s2. Trong cả tiếng kêu và g ++.

10

Tôi có một ví dụ sẽ cho thấy sự khác biệt:

#include <iostream>

using namespace std;
class A 
{
public:
    int x;
    A(){}
};

class B 
{
public:
    int x;
    B()=default;
};


int main() 
{ 
    int x = 5;
    new(&x)A(); // Call for empty constructor, which does nothing
    cout << x << endl;
    new(&x)B; // Call for default constructor
    cout << x << endl;
    new(&x)B(); // Call for default constructor + Value initialization
    cout << x << endl;
    return 0; 
} 

Đầu ra:

5
5
0

Như chúng ta có thể thấy lệnh gọi hàm tạo A () trống không khởi tạo các thành viên, trong khi B () thực hiện nó.


7
bạn vui lòng giải thích cú pháp này -> new (& x) A ();
Vencat

5
Chúng tôi đang tạo đối tượng mới trong bộ nhớ bắt đầu từ địa chỉ của biến x (thay vì cấp phát bộ nhớ mới). Cú pháp này được sử dụng để tạo đối tượng tại bộ nhớ được phân bổ trước. Như trong trường hợp của chúng tôi, kích thước của B = kích thước của int, do đó, mới (& x) A () sẽ tạo đối tượng mới ở vị trí của biến x.
Slavenskij

Cảm ơn lời giải thích của bạn.
Vencat

1
Tôi nhận được kết quả khác nhau với gcc 8.3: ideone.com/XouXux
Adam.Er8

Ngay cả với C ++ 14, tôi cũng nhận được các kết quả khác nhau: ideone.com/CQphuT
Mayank Bhushan

9

n2210 cung cấp một số lý do:

Việc quản lý mặc định có một số vấn đề:

  • Định nghĩa xây dựng được kết hợp; khai báo bất kỳ constructor nào ngăn chặn constructor mặc định.
  • Mặc định hàm hủy không phù hợp với các lớp đa hình, đòi hỏi một định nghĩa rõ ràng.
  • Một khi một mặc định bị chặn, không có cách nào để phục hồi nó.
  • Việc triển khai mặc định thường hiệu quả hơn so với triển khai được chỉ định thủ công.
  • Việc triển khai không mặc định là không tầm thường, điều này ảnh hưởng đến ngữ nghĩa loại, ví dụ: tạo ra loại không POD.
  • Không có cách nào để cấm một chức năng thành viên đặc biệt hoặc nhà điều hành toàn cầu mà không tuyên bố một sự thay thế (không tầm thường).

type::type() = default;
type::type() { x = 3; }

Trong một số trường hợp, phần thân lớp có thể thay đổi mà không yêu cầu thay đổi định nghĩa hàm thành viên vì mặc định thay đổi với khai báo của các thành viên bổ sung.

Xem Rule-of-Three trở thành Rule-of-Five với C ++ 11? :

Lưu ý rằng hàm tạo di chuyển và toán tử gán di chuyển sẽ không được tạo cho một lớp khai báo rõ ràng bất kỳ hàm thành viên đặc biệt nào khác, hàm tạo sao chép và toán tử gán gán sao chép sẽ không được tạo cho một lớp khai báo rõ ràng hàm tạo di chuyển hoặc di chuyển toán tử gán, và một lớp với hàm hủy được khai báo rõ ràng và hàm tạo sao chép được định nghĩa ngầm định hoặc toán tử gán gán sao chép được định nghĩa ngầm được coi là không dùng nữa


1
Chúng là những lý do để = defaultnói chung, hơn là lý do để thực hiện = defaulttrên một hàm tạo so với thực hiện { }.
Joseph Mansfield

@JosephMansfield Đúng, nhưng vì {}đã là một tính năng của ngôn ngữ trước khi sự ra đời của =default, những lý do này làm ngầm dựa vào sự khác biệt (ví dụ như "không có phương tiện để hồi sinh [a mặc định đàn áp]" ngụ ý rằng {}không tương đương với mặc định ).
Kyle Strand

7

Đó là một vấn đề ngữ nghĩa trong một số trường hợp. Nó không rõ ràng lắm với các hàm tạo mặc định, nhưng nó trở nên rõ ràng với các hàm thành viên do trình biên dịch khác tạo ra.

Đối với hàm tạo mặc định, có thể làm cho bất kỳ hàm tạo mặc định nào có phần thân trống được coi là một ứng cử viên để trở thành hàm tạo tầm thường, giống như sử dụng =default. Rốt cuộc, các hàm tạo mặc định trống cũ là C ++ hợp pháp .

struct S { 
  int a; 
  S() {} // legal C++ 
};

Việc trình biên dịch có hiểu hàm tạo này là tầm thường hay không là không liên quan trong hầu hết các trường hợp ngoài tối ưu hóa (thủ công hoặc trình biên dịch).

Tuy nhiên, nỗ lực này để coi các cơ quan chức năng trống là "mặc định" bị phá vỡ hoàn toàn cho các loại chức năng thành viên khác. Hãy xem xét các hàm tạo sao chép:

struct S { 
  int a; 
  S() {}
  S(const S&) {} // legal, but semantically wrong
};

Trong trường hợp trên, hàm tạo sao chép được viết với phần thân trống bây giờ là sai . Nó không còn thực sự sao chép bất cứ điều gì. Đây là một bộ ngữ nghĩa rất khác so với ngữ nghĩa của hàm tạo sao chép mặc định. Hành vi mong muốn yêu cầu bạn viết một số mã:

struct S { 
  int a; 
  S() {}
  S(const S& src) : a(src.a) {} // fixed
};

Tuy nhiên, ngay cả với trường hợp đơn giản này, nó trở thành gánh nặng cho trình biên dịch khi xác minh rằng hàm tạo sao chép giống hệt với trình tạo mà nó sẽ tự tạo hoặc để thấy rằng hàm tạo sao chép là tầm thường (tương đương với a memcpy, về cơ bản ). Trình biên dịch sẽ phải kiểm tra từng biểu thức khởi tạo thành viên và đảm bảo nó giống hệt với biểu thức để truy cập thành viên tương ứng của nguồn và không có gì khác, đảm bảo không có thành viên nào bị bỏ mặc với cấu trúc mặc định không tầm thường, v.v. trình biên dịch sẽ sử dụng để xác minh rằng các phiên bản được tạo riêng của hàm này là tầm thường.

Hãy xem xét sau đó toán tử gán sao chép có thể nhận được cả hairier, đặc biệt là trong trường hợp không tầm thường. Đó là một tấn nồi hơi mà bạn không muốn phải viết cho nhiều lớp nhưng dù sao bạn cũng bị buộc phải có trong C ++ 03:

struct T { 
  std::shared_ptr<int> b; 
  T(); // the usual definitions
  T(const T&);
  T& operator=(const T& src) {
    if (this != &src) // not actually needed for this simple example
      b = src.b; // non-trivial operation
    return *this;
};

Đó là một trường hợp đơn giản, nhưng nó đã có nhiều mã hơn bạn muốn buộc phải viết cho một loại đơn giản như vậy T(đặc biệt là khi chúng ta đưa các hoạt động di chuyển vào hỗn hợp). Chúng ta không thể dựa vào một cơ thể trống có nghĩa là "điền vào mặc định" bởi vì cơ thể trống đã hoàn toàn hợp lệ và có ý nghĩa rõ ràng. Trong thực tế, nếu phần thân trống được sử dụng để biểu thị "điền vào mặc định" thì sẽ không có cách nào để tạo ra một hàm tạo sao chép không tương tự hoặc tương tự.

Đây lại là một vấn đề nhất quán. Cơ thể trống rỗng có nghĩa là "không làm gì" nhưng đối với những thứ như các nhà xây dựng sao chép, bạn thực sự không muốn "không làm gì" mà là "làm tất cả những việc bạn thường làm nếu không bị đàn áp". Do đó =default. Nó là cần thiết để khắc phục các hàm thành viên do trình biên dịch bị chặn như sao chép / di chuyển các hàm tạo và toán tử gán. Sau đó, nó chỉ là "hiển nhiên" để làm cho nó hoạt động cho hàm tạo mặc định.

Có thể thật tuyệt khi tạo hàm tạo mặc định với các phần tử trống và các hàm tạo thành viên / cơ sở tầm thường cũng được coi là tầm thường giống như khi chúng =defaultchỉ làm cho mã cũ tối ưu hơn trong một số trường hợp, nhưng hầu hết mã cấp thấp dựa vào tầm thường các hàm tạo mặc định để tối ưu hóa cũng dựa vào các hàm tạo sao chép tầm thường. Nếu bạn sẽ phải đi và "sửa chữa" tất cả các hàm tạo bản sao cũ của mình, thì thực sự cũng không có gì nhiều để phải sửa tất cả các hàm tạo mặc định cũ của bạn. Nó cũng rõ ràng hơn và rõ ràng hơn bằng cách sử dụng một từ rõ ràng =defaultđể biểu thị ý định của bạn.

Có một vài điều khác mà các hàm thành viên do trình biên dịch tạo ra sẽ làm mà bạn phải thực hiện thay đổi rõ ràng để hỗ trợ. Hỗ trợ constexprcho các nhà xây dựng mặc định là một ví dụ. Nó chỉ dễ sử dụng về mặt tinh thần =defaulthơn là phải đánh dấu các chức năng với tất cả các từ khóa đặc biệt khác và những thứ đó được ngụ ý =defaultvà đó là một trong những chủ đề của C ++ 11: làm cho ngôn ngữ dễ dàng hơn. Nó vẫn còn nhiều mụn cóc và thỏa hiệp ngược lại nhưng rõ ràng đó là một bước tiến lớn từ C ++ 03 khi nói đến việc dễ sử dụng.


Tôi đã có một vấn đề mà tôi mong đợi = defaultsẽ làm a=0;và không! Tôi đã phải bỏ nó để ủng hộ : a(0). Tôi vẫn còn bối rối về sự hữu ích của = defaulttho, nó là về hiệu suất? nó sẽ phá vỡ ở đâu đó nếu tôi không sử dụng = default? Tôi đã cố gắng đọc tất cả các câu trả lời ở đây mua Tôi mới biết một số công cụ c ++ và tôi gặp nhiều khó khăn để hiểu nó.
Sức mạnh Bảo Bình

@AquariusPower: không chỉ "về" hiệu suất mà còn được yêu cầu trong một số trường hợp xung quanh các trường hợp ngoại lệ và ngữ nghĩa khác. Cụ thể, một toán tử mặc định có thể là tầm thường nhưng toán tử không mặc định không bao giờ là tầm thường và một số mã sẽ sử dụng các kỹ thuật lập trình meta để thay đổi hành vi cho hoặc thậm chí không cho phép các loại với các hoạt động không tầm thường. a=0Ví dụ của bạn là do hành vi của các loại tầm thường, là một chủ đề riêng biệt (mặc dù có liên quan).
Sean Middleditch

nó có nghĩa là nó có thể có = defaultvà vẫn asẽ cấp =0? một cách nào đó? Bạn có nghĩ rằng tôi có thể tạo một câu hỏi mới như "làm thế nào để có một hàm tạo = defaultvà cấp các trường sẽ được khởi tạo đúng không?", btw Tôi gặp vấn đề trong một structvà không phải là một class, và ứng dụng đang chạy chính xác ngay cả khi không sử dụng = default, tôi có thể thêm một cấu trúc tối thiểu vào câu hỏi đó nếu nó là một câu hỏi hay :)
Sức mạnh của Bảo Bình

1
@AquariusPower: bạn có thể sử dụng bộ khởi tạo thành viên dữ liệu không tĩnh. Viết cấu trúc của bạn như vậy: struct { int a = 0; };Nếu sau đó bạn quyết định bạn cần một nhà xây dựng, bạn có thể mặc định nó, nhưng lưu ý rằng loại sẽ không tầm thường (điều này tốt).
Sean Middleditch

2

Do sự phản đối std::is_podvà thay thế của nó std::is_trivial && std::is_standard_layout, đoạn trích từ câu trả lời của @JosephMansfield trở thành:

#include <type_traits>

struct X {
    X() = default;
};

struct Y {
    Y() {}
};

int main() {
    static_assert(std::is_trivial_v<X>, "X should be trivial");
    static_assert(std::is_standard_layout_v<X>, "X should be standard layout");

    static_assert(!std::is_trivial_v<Y>, "Y should not be trivial");
    static_assert(std::is_standard_layout_v<Y>, "Y should be standard layout");
}

Lưu ý rằng Yvẫn là bố trí tiêu chuẩn.

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.