Vòng lặp dựa trên phạm vi C ++ 11: lấy mục theo giá trị hoặc tham chiếu đến const


215

Đọc một số ví dụ về các vòng dựa trên phạm vi, họ đề xuất hai cách chính 1 , 2 , 3 , 4

std::vector<MyClass> vec;

for (auto &x : vec)
{
  // x is a reference to an item of vec
  // We can change vec's items by changing x 
}

hoặc là

for (auto x : vec)
{
  // Value of x is copied from an item of vec
  // We can not change vec's items by changing x
}

Tốt.

Khi chúng tôi không cần thay đổi vecvật phẩm, IMO, Ví dụ đề xuất sử dụng phiên bản thứ hai (theo giá trị). Tại sao họ không đề xuất một cái gì đó consttham khảo (Ít nhất là tôi không tìm thấy bất kỳ đề xuất trực tiếp nào):

for (auto const &x : vec) // <-- see const keyword
{
  // x is a reference to an const item of vec
  // We can not change vec's items by changing x 
}

Có tốt hơn không? Nó không tránh được một bản sao thừa trong mỗi lần lặp trong khi nó là một const?

Câu trả lời:


390

Nếu bạn không muốn thay đổi các mục cũng như muốn tránh sao chép, thì đó auto const &là lựa chọn chính xác:

for (auto const &x : vec)

Bất cứ ai đề nghị bạn sử dụng auto &là sai. Mặc kệ họ.

Đây là tóm tắt:

  • Chọn auto xkhi bạn muốn làm việc với các bản sao.
  • Chọn auto &xkhi bạn muốn làm việc với các mục gốc và có thể sửa đổi chúng.
  • Chọn auto const &xkhi bạn muốn làm việc với các mục gốc và sẽ không sửa đổi chúng.

21
Cảm ơn câu trả lời tuyệt vời. Tôi đoán nó cũng nên được chỉ ra const auto &xlà tương đương với lựa chọn thứ ba của bạn.
smg

7
@mloskot: Nó tương đương. (và ý của bạn là "nhưng đó là cùng một cú pháp" ? Cú pháp có thể khác biệt rõ rệt.)
Nawaz

14
Thiếu: auto&&khi bạn không muốn tạo một bản sao không cần thiết và không quan tâm bạn có sửa đổi hay không và bạn chỉ muốn làm việc.
Yakk - Adam Nevraumont

4
Phân biệt tham chiếu có áp dụng cho các bản sao nếu bạn chỉ làm việc với các loại cơ bản như int / double?

4
@racarate: Tôi không thể nhận xét về tốc độ chung và tôi nghĩ không ai có thể, nếu không định hình trước. Thành thật mà nói, tôi sẽ không dựa trên sự lựa chọn của tôi về tốc độ, thay vì sự rõ ràng của mã. Nếu tôi muốn bất biến, tôi constchắc chắn sẽ sử dụng . Tuy nhiên, cho dù đó là auto const &, hoặc auto const có ít sự khác biệt. Tôi sẽ chọn auto const &chỉ để phù hợp hơn.
Nawaz

23

Nếu bạn có một std::vector<int>hoặc std::vector<double>, thì nó chỉ tốt để sử dụng auto(với bản sao giá trị) thay vì const auto&, vì sao chép một inthoặc một doublegiá rẻ:

for (auto x : vec)
    ....

Nhưng nếu bạn có một std::vector<MyClass>, nơi MyClasscó một số ngữ nghĩa sao chép không tầm thường (ví dụ: std::stringmột số lớp tùy chỉnh phức tạp, v.v.) thì tôi khuyên bạn nên sử dụng const auto&để tránh các bản sao sâu :

for (const auto & x : vec)
    ....

3

Khi chúng tôi không cần thay đổi vecvật phẩm, các ví dụ đề nghị sử dụng phiên bản đầu tiên.

Sau đó, họ đưa ra một đề nghị sai.

Tại sao họ không đề xuất một cái gì đó mà const tham khảo

Bởi vì họ đưa ra một gợi ý sai :-) Những gì bạn đề cập là chính xác. Nếu bạn chỉ muốn quan sát một đối tượng, không cần phải tạo một bản sao và không cần phải không consttham chiếu đến nó.

BIÊN TẬP:

Tôi thấy các tài liệu tham khảo mà bạn liên kết đều cung cấp các ví dụ về việc lặp qua một loạt các intgiá trị hoặc một số loại dữ liệu cơ bản khác. Trong trường hợp đó, vì sao chép một bản sao intkhông tốn kém, việc tạo một bản sao về cơ bản tương đương với (nếu không hiệu quả hơn) có một quan sát const &.

Tuy nhiên, đây không phải là trường hợp chung cho các loại do người dùng xác định. UDT có thể tốn kém để sao chép và nếu bạn không có lý do để tạo một bản sao (chẳng hạn như sửa đổi đối tượng đã truy xuất mà không thay đổi bản gốc), thì nên sử dụng a const &.


1

Tôi sẽ trái ngược ở đây và nói rằng không cần auto const &trong một phạm vi dựa trên vòng lặp. Hãy cho tôi biết nếu bạn nghĩ rằng chức năng sau đây là ngớ ngẩn (không phải trong mục đích của nó, nhưng theo cách nó được viết):

long long SafePop(std::vector<uint32_t>& v)
{
    auto const& cv = v;
    long long n = -1;
    if (!cv.empty())
    {
        n = cv.back();
        v.pop_back();
    }
    return n;
}

Ở đây, tác giả đã tạo ra một tài liệu tham khảo const v để sử dụng cho tất cả các hoạt động không sửa đổi v. Theo tôi, điều này thật ngớ ngẩn và có thể đưa ra lập luận tương tự để sử dụng auto const &làm biến trong phạm vi dựa trên vòng lặp thay vì chỉ auto &.


3
@BenjaminLindley: Vì vậy, tôi có thể suy luận rằng bạn cũng sẽ tranh luận const_iteratortrong một vòng lặp for? Làm thế nào để bạn đảm bảo rằng bạn không thay đổi các mục ban đầu từ một container khi bạn lặp lại nó?
Nawaz

2
@BenjaminLindley: Tại sao mã của bạn không được biên dịch mà không có const_iterator? Hãy để tôi đoán, container mà bạn lặp đi lặp lại, là const. Nhưng tại sao nó constlại bắt đầu? Một nơi nào đó bạn đang sử dụng constđể đảm bảo những gì?
Nawaz

8
Lợi thế của việc tạo đối tượng constcũng giống như lợi thế của việc sử dụng private/ protectedtrong các lớp. Nó tránh những sai lầm hơn nữa.
masoud

2
@BenjaminLindley: Ồ, tôi đã bỏ qua điều đó. Sau đó, trong trường hợp này, việc thực hiện là ngớ ngẩn là tốt. Nhưng nếu chức năng đó không sửa đổi v, việc triển khai sẽ ổn IMO. Và nếu vòng lặp không bao giờ sửa đổi các đối tượng mà nó lặp đi lặp lại, thì có vẻ như tôi có quyền tham chiếu const. Mặc dù vậy, tôi vân nhìn thấy điểm của bạn. Của tôi là vòng lặp đại diện cho một đơn vị mã khá giống với chức năng và trong đơn vị đó không có hướng dẫn nào cần sửa đổi giá trị. Do đó, bạn có thể "gắn thẻ" toàn bộ đơn vị constnhư bạn sẽ làm với constchức năng thành viên.
Andy Prowl

1
Vì vậy, bạn nghĩ rằng const &dựa trên phạm vi là ngớ ngẩn, bởi vì bạn đã viết một ví dụ tưởng tượng không liên quan của một lập trình viên tưởng tượng, người đã làm điều gì đó ngớ ngẩn với cv -qualization? ... OK rồi
underscore_d
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.