Tôi không thể ngủ đêm qua và bắt đầu nghĩ về std::swap
. Đây là phiên bản C++ 98 quen thuộc:Hoán đổi nhanh hơn, dễ sử dụng và ngoại lệ an toàn
template <typename T>
void swap(T& a, T& b)
{
T c(a);
a = b;
b = c;
}
Nếu người dùng xác định loại Foo
sử dụng tài nguyên bên ngoài, điều này không hiệu quả. Thành ngữ phổ biến là cung cấp phương thức void Foo::swap(Foo& other)
và chuyên môn hóa std::swap<Foo>
. Lưu ý rằng điều này không hoạt động với các mẫu lớp vì bạn không thể chuyên biệt hóa một phần mẫu chức năng và quá tải tên trong không gian tên std
là bất hợp pháp. Giải pháp là viết một hàm mẫu trong không gian tên của chính nó và dựa vào tra cứu phụ thuộc đối số để tìm nó. Điều này phụ thuộc nhiều vào khách hàng để thực hiện theo "thành ngữ using std::swap
" thay vì gọi trực tiếp std::swap
. Rất giòn.
Trong C++ 0x, nếu Foo
có một constructor di chuyển người dùng định nghĩa và một toán tử gán di chuyển, cung cấp một phương pháp swap
tùy chỉnh và một std::swap<Foo>
chuyên môn có ít hoặc không có hiệu quả lợi ích, vì phiên bản C++ 0x của std::swap
sử dụng các di chuyển hiệu quả thay vì bản sao:
#include <utility>
template <typename T>
void swap(T& a, T& b)
{
T c(std::move(a));
a = std::move(b);
b = std::move(c);
}
Không cần phải chịu khó với swap
nữa đã mất rất nhiều gánh nặng từ lập trình viên. Trình biên dịch hiện tại không tạo ra các nhà xây dựng di chuyển và di chuyển các toán tử gán tự động, nhưng theo như tôi biết, điều này sẽ thay đổi. Vấn đề duy nhất còn lại sau đó là ngoại lệ-an toàn, bởi vì nói chung, hoạt động di chuyển được phép ném, và điều này mở ra một toàn bộ có thể của sâu. Câu hỏi "Chính xác trạng thái của đối tượng chuyển từ là gì?" làm phức tạp thêm.
Sau đó, tôi đã suy nghĩ, những gì chính xác là ngữ nghĩa của std::swap
trong C++ 0x nếu mọi thứ diễn ra tốt đẹp? Trạng thái của các đối tượng trước và sau khi hoán đổi là gì? Thông thường, trao đổi thông qua các hoạt động di chuyển không chạm vào tài nguyên bên ngoài, chỉ có các đại diện "phẳng" đối tượng.
Vậy tại sao không chỉ đơn giản viết mẫu swap
thực hiện chính xác điều đó: trao đổi đại diện đối tượng?
#include <cstring>
template <typename T>
void swap(T& a, T& b)
{
unsigned char c[sizeof(T)];
memcpy(c, &a, sizeof(T));
memcpy(&a, &b, sizeof(T));
memcpy(&b, c, sizeof(T));
}
Tính năng này hiệu quả như: nó chỉ đơn giản là thổi qua bộ nhớ thô. Nó không yêu cầu bất kỳ sự can thiệp nào từ người dùng: không có phương thức hoán đổi đặc biệt hoặc các hoạt động di chuyển phải được xác định. Điều này có nghĩa rằng nó thậm chí hoạt động trong C++ 98 (mà không có tham chiếu rvalue, tâm trí bạn). Nhưng thậm chí quan trọng hơn, bây giờ chúng ta có thể quên đi các vấn đề ngoại lệ-an toàn, bởi vì memcpy
không bao giờ ném.
Tôi có thể thấy hai vấn đề tiềm ẩn với cách tiếp cận này:
Đầu tiên, không phải tất cả các đối tượng đều được đổi chỗ. Nếu một nhà thiết kế lớp ẩn bản sao xây dựng hoặc toán tử gán bản sao, cố gắng hoán đổi các đối tượng của lớp sẽ không thành công tại thời gian biên dịch. Chúng tôi chỉ có thể giới thiệu một số mã chết để kiểm tra xem việc sao chép và chuyển nhượng có hợp pháp với loại đó không:
template <typename T>
void swap(T& a, T& b)
{
if (false) // dead code, never executed
{
T c(a); // copy-constructible?
a = b; // assignable?
}
unsigned char c[sizeof(T)];
std::memcpy(c, &a, sizeof(T));
std::memcpy(&a, &b, sizeof(T));
std::memcpy(&b, c, sizeof(T));
}
Bất kỳ trình biên dịch nào cũng có thể loại bỏ mã chết. (Có lẽ có nhiều cách tốt hơn để kiểm tra "sự phù hợp hoán đổi", nhưng đó không phải là vấn đề. Điều quan trọng là có thể).
Thứ hai, một số loại có thể thực hiện các hành động "bất thường" trong trình tạo bản sao và sao chép toán tử gán. Ví dụ, họ có thể thông báo cho các nhà quan sát về sự thay đổi của họ. Tôi cho rằng đây là một vấn đề nhỏ, bởi vì các loại đối tượng như vậy có lẽ không nên cung cấp các hoạt động sao chép ngay từ đầu.
Vui lòng cho tôi biết suy nghĩ của bạn về cách tiếp cận này để trao đổi. Nó có hoạt động trong thực tế không? Bạn sẽ sử dụng nó? Bạn có thể xác định các loại thư viện mà điều này sẽ phá vỡ không? Bạn có thấy các vấn đề khác không? Bàn luận!
Hầu hết các trường hợp sử dụng hiện tại cho 'std :: swap' sẽ có các giải pháp tốt hơn bằng cách sử dụng ngữ nghĩa di chuyển. – aschepler
Có di chuyển ngữ nghĩa và di chuyển các nhà thầu. Xem này, http://stackoverflow.com/questions/4820643/understanding-stdswap-what-is-the-purpose-of-tr1-remove-reference –
xem xét 'hoán đổi (* polymorphicPtr1, * polymorphicPtr2)' ... của bạn hoán đổi chức năng sẽ trao đổi vtable của cả hai đối tượng là tốt ... mà sẽ gây ra tàn phá nếu ai đó gọi một chức năng ảo ofter cuộc gọi để trao đổi. – smerlin