2014-05-20 13 views
11

Lưu ý: Được hỏi bởi Matt Mcnabbcomment ngày Why can swapping standard library containers be problematic in C++11 (involving allocators)?.Tại sao cho phép `propagate_on_container_swap == false` trong Allocators, khi nó có thể gây ra hành vi không xác định?


Tiêu chuẩn (N3797) nói rằng nếu progagate_on_container_swap bên trong một cấp phátstd::false_type nó sẽ mang lại hành vi undefined nếu hai allocators tham gia không so sánh bằng nhau.

  • Tại sao tiêu chuẩn cho phép cấu trúc như vậy khi nó có vẻ nguy hiểm hơn?

Yêu cầu

23.2.1p9chung container[container.requirements.general]

Nếu allocator_traits<allocator_type>::propagate_on_container_swap::valuetrue thì allocators của ab cũng sẽ được trao đổi sử dụng một cuộc gọi unqalified để phi thành viên swap. Nếu không, chúng sẽ không được đổi chỗ và hành vi không xác định trừ khi a.get_allocator() == b.get_allocator().

Trả lời

10

tôi có thể nghĩ ra một vài tình huống thực tế cuộc sống nơi xây dựng được cho phép bởi các tiêu chuẩn cả hai có ý nghĩa, và là cần thiết, tuy nhiên; Trước tiên tôi sẽ cố gắng trả lời câu hỏi này từ góc độ rộng hơn, không liên quan đến bất kỳ vấn đề cụ thể nào.


giải thích

allocators là này điều kỳ diệu trách nhiệm bố trí, xây dựng, huỷ, và thu hồi bộ nhớ và các tổ chức. Kể từ khi C++ 11 khi phân bổ stateful đi vào chơi một cấp phát có thể làm nhiều hơn trước đây, nhưng tất cả nắm bắt xuống bốn hoạt động đã đề cập trước đó.

allocators có vô số yêu cầu, một trong số họ được rằng a1 == a2 (nơi a1a2 là allocators cùng loại) phải nhường truechỉ nếu bộ nhớ phân bổ bởi ai có thể deallocated bởi khác [1].

Yêu cầu trên là operator== có nghĩa là hai trình phân bổ so sánh bằng nhau có thể làm những việc khác nhau, miễn là chúng vẫn có sự hiểu biết lẫn nhau về cách phân bổ bộ nhớ.

Ở trên là lý do tại sao tiêu chuẩn cho phép propagate_on_container_* bằng std::false_type; chúng ta có thể muốn thay đổi nội dung của hai thùng chứa mà các trình phân bổ có cùng hành vi deallocation, nhưng để lại hành vi khác (không liên quan đến quản lý bộ nhớ cơ bản) phía sau.


[1] như đã nêu trong [allocator.requirements]p2 (bảng 28)


THE (ngớ ngẩn) CÂU CHUYỆN

Hãy tưởng tượng rằng chúng ta có một cấp phát tên là Watericator, nó tập hợp nước theo yêu cầu phân bổ, và đưa nó vào container yêu cầu.

Máy làm nước là một trình phân bổ đầy đủ, và khi xây dựng ví dụ của chúng tôi, chúng tôi có thể chọn hai chế độ;

  1. employ Eric, ai lấy về nước xuống ở suối nước ngọt, đồng thời cũng giải pháp (và báo cáo) mực nước và độ tinh khiết.

  2. sử dụng Adam, người sử dụng vòi ở sân sau và không quan tâm đến việc đăng nhập. Adam nhanh hơn rất nhiều so với Eric.


Không có vấn đề nơi nước xuất phát từ chúng tôi luôn vút của nó trong cùng một cách; bằng cách tưới cây. Ngay cả khi chúng tôi có một trường hợp trong đó Eric đang cấp nước cho chúng tôi (bộ nhớ) và một nơi khác Adam đang sử dụng vòi nước, cả hai Máy tưới nước so sánh bằng với số lượng operator==.

Phân bổ do người khác thực hiện có thể được người khác phân phối lại.


Trên đây có thể là một similie ngớ ngẩn, nhưng hãy tưởng tượng chúng ta có một cấp phát mà không đăng nhập trên mỗi phân bổ, và chúng tôi sử dụng này trên một container ở đâu đó trong mã của chúng tôi mà bạn quan tâm chúng ta; sau đó chúng tôi muốn chuyển các phần tử ra khỏi vùng chứa này sang vùng chứa khác .. nhưng chúng tôi không còn quan tâm đến tất cả việc ghi nhật ký đó nữa.

Without allocators stateful, và các tùy chọn để chuyển propagate_on_container_* tắt, chúng tôi sẽ buộc phải hoặc là 1) sao chép tất cả các yếu tố liên quan đến 2) bị mắc kẹt với điều đó (không longer required) khai thác gỗ.

0

Nó không phải là rất nhiều mà tiêu chuẩn cho phép propagate_on_container_swap-nguyên nhân Behavior Không xác định, nhưng điều đó chỉ số Standard lộ Behavior Không xác định thông qua giá trị này!


Một ví dụ đơn giản là xem xét một cấp phát scoped, mà phân bổ bộ nhớ từ một hồ bơi địa phương, và trong đó cho biết hồ bơi sẽ bị xóa khi bộ cấp phát đi ra khỏi phạm vi:

template <typename T> 
class scoped_allocator; 

Bây giờ, chúng ta hãy sử dụng nó:

int main() { 
    using scoped = scoped_allocator<int>; 

    scoped outer_alloc; 
    std::vector<int, scoped> outer{outer_alloc}; 

    outer.push_back(3); 

    { 
     scoped inner_alloc; 
     std::vector<int, scoped> inner{inner_alloc}; 

     inner.push_back(5); 

     swap(outer, inner); // Undefined Behavior: loading... 
    } 

    // inner_allocator is dead, but "outer" refers to its memory 
} 
Các vấn đề liên quan