Tôi có một trình bao bọc cho một số mã di sản.
class A{
L* impl_; // the legacy object has to be in the heap, could be also unique_ptr
A(A const&) = delete;
L* duplicate(){L* ret; legacy_duplicate(impl_, &L); return ret;}
... // proper resource management here
};
Trong mã kế thừa này, hàm mà Sao chép lại một đối tượng không phải là một chủ đề an toàn (khi gọi cùng một đối số đầu tiên), do đó nó không được đánh dấu const
trong trình bao bọc. Tôi đoán theo các quy tắc hiện đại: https://herbsutter.com/2013/01/01/video-you-dont-ledge-const-and-mutable/
Đây có duplicate
vẻ là một cách tốt để thực hiện một hàm tạo sao chép, ngoại trừ chi tiết không phải là nó const
. Vì vậy, tôi không thể làm điều này trực tiếp:
class A{
L* impl_; // the legacy object has to be in the heap
A(A const& other) : L{other.duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
Vậy đâu là lối thoát cho tình huống nghịch lý này?
(Chúng ta cũng nói rằng legacy_duplicate
nó không an toàn cho luồng nhưng tôi biết để đối tượng ở trạng thái ban đầu khi nó thoát. Là một hàm C, hành vi chỉ được ghi lại nhưng không có khái niệm về hằng số.)
Tôi có thể nghĩ về nhiều tình huống có thể xảy ra:
(1) Một khả năng là không có cách nào để thực hiện một hàm tạo sao chép với ngữ nghĩa thông thường cả. (Vâng, tôi có thể di chuyển đối tượng và đó không phải là thứ tôi cần.)
(2) Mặt khác, sao chép một đối tượng vốn không an toàn theo chủ đề theo nghĩa là sao chép một loại đơn giản có thể tìm thấy nguồn ở trạng thái được sửa đổi một nửa, vì vậy tôi có thể tiếp tục và làm điều này có lẽ,
class A{
L* impl_;
A(A const& other) : L{const_cast<A&>(other).duplicate()}{} // error calling a non-const function
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(3) hoặc thậm chí chỉ cần khai báo duplicate
const và nói dối về an toàn luồng trong tất cả các bối cảnh. (Sau khi tất cả các chức năng kế thừa không quan tâm const
nên trình biên dịch thậm chí sẽ không phàn nàn.)
class A{
L* impl_;
A(A const& other) : L{other.duplicate()}{}
L* duplicate() const{L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
(4) Cuối cùng, tôi có thể theo logic và tạo một hàm tạo sao chép có đối số không phải là const .
class A{
L* impl_;
A(A const&) = delete;
A(A& other) : L{other.duplicate()}{}
L* duplicate(){L* ret; legacy_duplicate(impl_, &ret); return ret;}
};
Nó chỉ ra rằng điều này hoạt động trong nhiều bối cảnh, bởi vì những đối tượng này thường không const
.
Câu hỏi là, đây là một tuyến đường hợp lệ hay phổ biến?
Tôi không thể đặt tên cho chúng, nhưng tôi trực giác mong đợi rất nhiều vấn đề trong quá trình có một nhà xây dựng bản sao không phải là const. Có lẽ nó sẽ không đủ điều kiện là một loại giá trị vì sự tinh tế này.
(5) Cuối cùng, mặc dù điều này có vẻ là quá mức cần thiết và có thể có chi phí thời gian chạy cao, tôi có thể thêm một mutex:
class A{
L* impl_;
A(A const& other) : L{other.duplicate_locked()}{}
L* duplicate(){
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
L* duplicate_locked() const{
std::lock_guard<std::mutex> lk(mut);
L* ret; legacy_duplicate(impl_, &ret); return ret;
}
mutable std::mutex mut;
};
Nhưng bị buộc phải làm điều này có vẻ như bi quan và làm cho lớp học lớn hơn. Tôi không chắc. Tôi hiện đang nghiêng về (4) , hoặc (5) hoặc kết hợp cả hai.
- EDIT
Một lựa chọn khác:
(6) Quên tất cả ý nghĩa của hàm thành viên trùng lặp và chỉ cần gọi legacy_duplicate
từ hàm tạo và khai báo rằng hàm tạo sao chép không phải là luồng an toàn. (Và nếu cần, hãy tạo một loại khác an toàn cho chủ đề của loại này, A_mt
)
class A{
L* impl_;
A(A const& other){legacy_duplicate(other.impl_, &impl_);}
};
CHỈNH SỬA 2
Đây có thể là một mô hình tốt cho những gì chức năng kế thừa làm. Lưu ý rằng bằng cách chạm vào đầu vào, cuộc gọi không phải là chuỗi an toàn đối với giá trị được biểu thị bằng đối số đầu tiên.
void legacy_duplicate(L* in, L** out){
*out = new L{};
char tmp = in[0];
in[0] = tmp;
std::memcpy(*out, in, sizeof *in); return;
}
legacy_duplicate
có thể được gọi với cùng một đối số đầu tiên từ hai luồng khác nhau.
const
nghĩa thực sự của nó. :-) Tôi sẽ không nghĩ hai lần về việc lấy một const&
bản sao của mình miễn là tôi không sửa đổi other
. Tôi luôn nghĩ về sự an toàn của luồng như một thứ gì đó thêm vào trên bất kỳ thứ gì cần được truy cập từ nhiều luồng, thông qua việc đóng gói và tôi thực sự mong chờ câu trả lời.
L
đó được sửa đổi bằng cách tạo mộtL
thể hiện mới ? Nếu không, tại sao bạn tin rằng hoạt động này không an toàn cho luồng?