Lý do đằng sau cbegin / cend là gì?


187

Tôi tự hỏi tại sao cbegincendđược giới thiệu trong C ++ 11?

Các trường hợp khi gọi các phương thức này tạo ra sự khác biệt từ quá tải const beginvà là endgì?

Câu trả lời:


226

Nó khá đơn giản. Nói rằng tôi có một vectơ:

std::vector<int> vec;

Tôi điền nó với một số dữ liệu. Sau đó, tôi muốn có được một số vòng lặp cho nó. Có lẽ vượt qua chúng xung quanh. Có thể để std::for_each:

std::for_each(vec.begin(), vec.end(), SomeFunctor());

Trong C ++ 03, SomeFunctorđã có thể tự do sửa đổi tham số mà nó nhận được. Chắc chắn, SomeFunctorcó thể lấy tham số của nó theo giá trị hoặc theo const&, nhưng không có cách nào để đảm bảo rằng nó làm được. Không phải không làm điều gì ngớ ngẩn như thế này:

const std::vector<int> &vec_ref = vec;
std::for_each(vec_ref.begin(), vec_ref.end(), SomeFunctor());

Bây giờ, chúng tôi giới thiệu cbegin/cend:

std::for_each(vec.cbegin(), vec.cend(), SomeFunctor());

Bây giờ, chúng tôi có sự đảm bảo rằng cú pháp SomeFunctorkhông thể sửa đổi các yếu tố của vector (mà không có một const-cast, tất nhiên). Chúng tôi rõ ràng nhận được const_iterators, và do đó SomeFunctor::operator()sẽ được gọi với const int &. Nếu nó lấy tham số của nó là int &, C ++ sẽ phát sinh lỗi trình biên dịch.


C ++ 17 có một giải pháp thanh lịch hơn cho vấn đề này : std::as_const. Chà, ít nhất là nó thanh lịch khi sử dụng dựa trên phạm vi for:

for(auto &item : std::as_const(vec))

Điều này chỉ đơn giản trả về một const&đối tượng mà nó được cung cấp.


1
Tôi nghĩ giao thức mới là cbegin (vec) chứ không phải vec.cbegin ().
Kaz Dragon

20
@Kaz: Không có std::cbegin/cendchức năng miễn phí theo cách std::begin/std::endtồn tại. Đó là một sự giám sát của ủy ban. Nếu những chức năng đó tồn tại, đó thường sẽ là cách sử dụng chúng.
Nicol Bolas

20
Rõ ràng, std::cbegin/cendsẽ được thêm vào trong C ++ 14. Xem en.cppreference.com/w/cpp/iterator/begin
Adi Shavit

8
@NicolBolas for(auto &item : std::as_const(vec))tương đương với for(const auto &item : vec)?
luizfls

8
@luizfls Vâng. Mã của bạn cho biết mục này sẽ không được sửa đổi bằng cách đưa constvào tham chiếu. Nicol's xem container là const, do đó autosuy ra một consttài liệu tham khảo. IMO auto const& itemdễ dàng và rõ ràng hơn. Không rõ tại sao lại std::as_const()tốt ở đây; Tôi có thể thấy nó sẽ hữu ích khi chuyển một constmã không chung sang mã chung mà chúng ta không thể kiểm soát loại được sử dụng, nhưng với phạm vi- for, chúng ta có thể, vì vậy nó giống như thêm tiếng ồn cho tôi ở đó.
gạch dưới

66

Ngoài những gì Nicol Bolas đã nói trong câu trả lời của mình , hãy xem xét autotừ khóa mới :

auto iterator = container.begin();

Với auto, không có cách nào để đảm bảo rằng begin()trả về một toán tử không đổi cho tham chiếu vùng chứa không liên tục. Vì vậy, bây giờ bạn làm:

auto const_iterator = container.cbegin();

2
@allyourcode: Không giúp được gì. Đối với trình biên dịch, const_iteratorchỉ là một định danh khác. Cả hai phiên bản đều không sử dụng tra cứu các typedefs thông thường decltype(container)::iteratorhoặc decltype(container)::const_iterator.
aschepler

2
@aschepler Tôi không hiểu câu thứ hai của bạn, nhưng tôi nghĩ bạn đã bỏ lỡ "const" trước "auto" trong câu hỏi của tôi. Bất cứ điều gì tự động đến, dường như const_iterator nên là const.
mã đồng minh

26
@allyourcode: Điều đó sẽ cung cấp cho bạn một trình vòng lặp không đổi, nhưng điều đó rất khác so với trình vòng lặp với dữ liệu không đổi.
aschepler

2
Có một cách đơn giản để đảm bảo rằng bạn nhận được một const_iteratorvới auto: Viết một mẫu chức năng phụ trợ gọi là make_constđể đủ điều kiện tham số đối tượng.
Columbo

17
Có lẽ tôi không còn trong tư duy C ++ nữa, nhưng tôi không thể thấy mối liên hệ giữa các khái niệm "một cách đơn giản" và "viết một mẫu hàm phụ trợ". ;)
Stefan Majewsky

15

Hãy coi đây là một usecase thực tế

void SomeClass::f(const vector<int>& a) {
  auto it = someNonConstMemberVector.begin();
  ...
  it = a.begin();
  ...
}

Việc chuyển nhượng thất bại vì itlà một trình lặp không lặp. Nếu bạn sử dụng cbegin ban đầu, iterator sẽ có đúng loại.


8

Từ http://www.open-std.org/jtc1/sc22/wg21/docs/ con / 2004 / n1674.pdf :

để một lập trình viên có thể trực tiếp lấy const_iterator từ ngay cả một thùng chứa không phải const

Họ đã đưa ra ví dụ này

vector<MyType> v;

// fill v ...
typedef vector<MyType>::iterator iter;
for( iter it = v.begin(); it != v.end(); ++it ) {
    // use *it ...
}

Tuy nhiên, khi truyền tải container chỉ nhằm mục đích kiểm tra, việc sử dụng const_iterator để cho phép trình biên dịch chẩn đoán vi phạm const-orthity là một cách phổ biến.

Lưu ý rằng bản nghiên cứu cũng đề cập đến bộ chuyển đổi mẫu, mà bây giờ đã được hoàn thiện như std::begin()std::end()và đó cũng làm việc với mảng bản địa. Tương ứng std::cbegin()std::cend()bị mất tích một cách tò mò vào thời điểm này, nhưng chúng cũng có thể được thêm vào.


5

Chỉ vấp phải câu hỏi này ... Tôi biết đó là câu trả lời tuyệt vời và nó chỉ là một nút bên ...

auto const it = container.begin() là một loại khác nhau auto it = container.cbegin()

sự khác biệt cho int[5](sử dụng con trỏ, mà tôi biết không có phương thức bắt đầu nhưng chỉ ra sự khác biệt ... nhưng sẽ hoạt động trong c ++ 14 std::cbegin()std::cend(), về cơ bản là những gì người ta nên sử dụng khi ở đây) ...

int numbers = array[7];
const auto it = begin(numbers); // type is int* const -> pointer is const
auto it = cbegin(numbers);      // type is int const* -> value is const

2

iteratorconst_iteratorcó mối quan hệ thừa kế và một chuyển đổi ngầm xảy ra khi so sánh với hoặc được gán cho loại khác.

class T {} MyT1, MyT2, MyT3;
std::vector<T> MyVector = {MyT1, MyT2, MyT3};
for (std::vector<T>::const_iterator it=MyVector.begin(); it!=MyVector.end(); ++it)
{
    // ...
}

Sử dụng cbegin()cend()sẽ tăng hiệu suất trong trường hợp này.

for (std::vector<T>::const_iterator it=MyVector.cbegin(); it!=MyVector.cend(); ++it)
{
    // ...
}

Phải mất một thời gian tôi mới nhận ra rằng hiệu suất của bạn được lưu bằng cách tránh chuyển đổi khi khởi tạo và so sánh các trình vòng lặp, không phải là huyền thoại phổ biến mà constlợi ích chính là hiệu suất (không phải là: mã chính xác và an toàn về mặt ngữ nghĩa). Nhưng, trong khi bạn có một điểm, (A) autolàm cho điều đó không thành vấn đề; (B) khi nói về hiệu suất, bạn đã bỏ lỡ một điều chính bạn nên làm ở đây: lưu trữ bộ endlặp bằng cách khai báo một bản sao của nó trong điều kiện ban đầu của forvòng lặp, và so sánh với điều đó, thay vì nhận một bản sao mới bằng giá trị cho mỗi lần lặp. Điều đó sẽ làm cho quan điểm của bạn tốt hơn. : P
underscore_d

@underscore_d constchắc chắn có thể giúp đạt được hiệu suất tốt hơn, không phải vì một số phép thuật trong constchính từ khóa mà bởi vì trình biên dịch có thể kích hoạt một số tối ưu hóa nếu biết rằng dữ liệu sẽ không được sửa đổi, điều này sẽ không thể xảy ra. Kiểm tra bit này từ bài nói chuyện của Jason Turner để biết ví dụ trực tiếp về điều này.
brainplot

@brainplot Tôi không nói là không thể. Tôi đã nói rằng đó không phải là lợi ích chính của nó và tôi nghĩ rằng nó bị cường điệu hóa, khi lợi ích thực sự của nó là mã chính xác và an toàn về mặt ngữ nghĩa.
gạch dưới

@underscore_d Vâng, tôi đồng ý về điều đó. Tôi chỉ nói rõ rằng constcó thể (gần như gián tiếp) dẫn đến lợi ích hiệu suất; chỉ trong trường hợp ai đó đang đọc điều này có thể nghĩ rằng "Tôi sẽ không bận tâm thêm constnếu mã được tạo không bị ảnh hưởng theo bất kỳ cách nào", điều đó không đúng.
brainplot

0

đơn giản, cbegin trả về một trình vòng lặp không đổi trong đó bắt đầu trả về chỉ là một trình vòng lặp

để hiểu rõ hơn, hãy lấy hai kịch bản ở đây

cảnh 1 :

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.begin();i< v.end();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

Điều này sẽ chạy vì ở đây iterator i không phải là hằng số và có thể tăng thêm 5

bây giờ, hãy sử dụng cbegin và ký hiệu biểu thị chúng dưới dạng kịch bản lặp không đổi - 2:

#include <iostream>
using namespace std;
#include <vector>
int main(int argc, char const *argv[])
{
std::vector<int> v;

for (int i = 1; i < 6; ++i)
{
    /* code */
    v.push_back(i);
}

for(auto i = v.cbegin();i< v.cend();i++){
    *i = *i + 5;
}

for (auto i = v.begin();i < v.end();i++){
    cout<<*i<<" ";
}

return 0;
}

điều này sẽ không hoạt động, bởi vì bạn không thể cập nhật giá trị bằng cách sử dụng cbegin và cend trả về trình lặp không đổi

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.