2014-09-24 20 views
5

Nếu bạn sử dụng C++ std :: map (và các vùng chứa khác) với các kiểu giá trị, bạn sẽ nhận thấy rằng chèn vào bản đồ gọi hàm hủy cho kiểu phần tử của bạn. Điều này là do việc thực hiện các operator [] là yêu cầu của C++ spec để được tương đương này:Tại sao không C++ std :: map :: operator [] sử dụng tại chỗ mới?

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second 

Nó kêu gọi các nhà xây dựng mặc định của loại hình của bạn để xây dựng cặp đó. Giá trị tạm thời đó sau đó được sao chép vào bản đồ và sau đó bị hủy. Xác nhận điều này có thể được tìm thấy trong this stackoverflow posthere on codeguru.

Điều tôi thấy lạ là điều này có thể được triển khai mà không cần biến tạm thời và vẫn tương đương. Có một tính năng của C++ được gọi là "inplace new". Bản đồ :: std và các vùng chứa khác có thể phân bổ không gian trống cho đối tượng để sống và sau đó gọi rõ ràng hàm tạo mặc định của phần tử trên không gian được phân bổ.

Câu hỏi của tôi: Tại sao không có triển khai std :: bản đồ mà tôi đã thấy sử dụng tại chỗ mới để tối ưu hóa hoạt động này? Dường như với tôi rằng nó sẽ cải thiện đáng kể hiệu suất của hoạt động cấp thấp này. Nhưng nhiều con mắt đã nghiên cứu cơ sở mã STL, vì vậy tôi con số đó phải có một số lý do nó được thực hiện theo cách này.

+0

Trường hợp kiểm tra được bao gồm trong bài đăng stackoverflow được liên kết. Đây là liên kết một lần nữa: https://stackoverflow.com/questions/4017892/in-an-stl-map-of-structs-why-does-the-operator-cause-the-structs-dtor-to – srm

+0

Mike Seymour: Vì vậy, bạn đang đề xuất rằng với trình gỡ lỗi đã tắt, các cuộc gọi thêm vào trình phá hủy sẽ biến mất? Bạn có biết đó là sự thật không? Ngay cả khi nó là đúng, nó có nghĩa là với gỡ lỗi trên, thực hiện là khó khăn hơn để gỡ lỗi (tôi không thể chỉ cần thiết lập một breakpoint trong trình gỡ rối của tôi tìm kiếm các vấn đề phạm vi "thực"). Nó cũng kém hiệu quả hơn trong khi gỡ lỗi, đó là một vấn đề nhỏ hơn, nhưng vẫn còn thực. Cả hai lý do này dường như là lý do để tôi sử dụng thay thế mới cho thư viện cấp thấp như vậy. – srm

Trả lời

4

Nói chung, bạn chỉ định hoạt động cấp cao hơn như [] về mặt cấp độ thấp hơn là một ý tưởng hay.

Trước C++ 11, làm như vậy với [] sẽ rất khó mà không cần sử dụng insert.

Trong C++ 11, việc thêm std::map<?>::emplace và các nội dung tương tự cho std::pair cho chúng tôi khả năng tránh sự cố đó. Nếu bạn định nghĩa lại nó để sử dụng như vậy tại chỗ xây dựng, thêm (hy vọng elided) đối tượng sáng tạo sẽ biến mất.

Tôi không thể nghĩ ra lý do nào không làm điều này. Tôi sẽ khuyến khích bạn đề xuất nó để chuẩn hóa.

Để chứng minh bản sao-less chèn vào một std::map, chúng ta có thể làm như sau:

#include <map> 
#include <iostream> 

struct no_copy_type { 
    no_copy_type(no_copy_type const&)=delete; 
    no_copy_type(double) {} 
    ~no_copy_type() { std::cout << "destroyed\n"; } 
}; 
int main() { 
    std::map< int, no_copy_type > m; 
    m.emplace(
    std::piecewise_construct, 
    std::forward_as_tuple(1), 
    std::forward_as_tuple(3.14) 
); 
    std::cout << "destroy happens next:\n"; 
} 

live example - như bạn có thể nhìn thấy, không có tạm thời được tạo ra.

Vì vậy, nếu chúng ta thay thế

(*((std::map<>::insert(std::make_pair(x, T()))).first)).second 

với

(* 
    (
    (
     std::map<>::emplace(
     std::piecewise_construct, 
     std::forward_as_tuple(std::forward<X>(x)), 
     std::forward_as_tuple() 
    ) 
).first 
).second 

không tạm thời sẽ được tạo ra (khoảng trắng thêm để tôi có thể theo dõi các () s).

+0

Câu trả lời sớm hơn từ Mike Seymour cho thấy khả năng lập trình để chọn một Allocator thay thế có nghĩa là việc xây dựng tạm thời không thể bị xóa khỏi mã và phải được elided trong quá trình tối ưu hóa. người dùng mới tại sao một destructor được gọi trong quá trình chèn. – srm

+0

@srm Chắc chắn rồi. Nhưng Mike đã sai. Đó là một lỗi nhỏ (không hiệu quả) trong tiêu chuẩn, giả sử nó không phải là đã được cố định (tôi đã không kiểm tra mới nhất). Các nhà phân phối có thể được phục hồi, và xây dựng emplecewise và piecewise có thể cho phép bạn xây dựng các đối tượng mà không cần bất kỳ tạm thời. Việc xây dựng mặc định có thể được thực hiện thông qua một 'tuple' trống. – Yakk

+0

Tôi không biết đủ để bình luận. Đó là lý do tại sao tôi hỏi câu hỏi. Bạn có thể gửi bình luận của bạn lên câu trả lời của Mike để anh ấy nhận được thông báo và xem liệu hai người có thể thuyết phục nhau không? – srm

0

Trước hết, operator[<key>] cho std::map chỉ tương đương với thao tác chèn nếu yêu cầu <key> không được tìm thấy. Trong trường hợp này, chỉ một tham chiếu đến khóa là cần thiết và chỉ một tham chiếu đến giá trị được lưu trữ được tạo ra.

Thứ hai, khi phần tử mới được chèn vào, không có cách nào để biết liệu thao tác sao chép có theo sau hay không. Bạn có thể có map[_k] = _v; hoặc bạn có thể có _v = map[_k];.Sau đó, khóa học có cùng các yêu cầu như bên ngoài nhiệm vụ, tức là map[_k].method_call();, nhưng không không sử dụng hàm tạo bản sao (không có nguồn để xây dựng từ). Về chèn, tất cả các bên trên yêu cầu hàm tạo mặc định của số value_type được gọi và không gian đó được phân bổ cho nó. Ngay cả khi chúng ta có thể biết khi viết operator[] mà chúng tôi đã sử dụng trong trường hợp chuyển nhượng, chúng tôi không thể sử dụng "inplace new" vì thứ tự các hoạt động. Các nhà xây dựng value_type sẽ phải được gọi là đầu tiên, theo sau là value_type::operator=, mà đòi hỏi sự kêu gọi của các nhà xây dựng bản sao.

Suy nghĩ hay.

Các vấn đề liên quan