Khi tôi học C ++ từ lâu, tôi đã nhấn mạnh với tôi rằng một phần của quan điểm của C ++ là giống như các vòng lặp có "vòng lặp bất biến", các lớp cũng có bất biến liên quan đến thời gian tồn tại của đối tượng - những điều đó phải là sự thật cho đến khi đối tượng còn sống. Những thứ nên được thiết lập bởi các nhà xây dựng, và được bảo tồn bằng các phương thức. Đóng gói / kiểm soát truy cập là có để giúp bạn thực thi các bất biến. RAII là một điều mà bạn có thể làm với ý tưởng này.
Kể từ C ++ 11, chúng tôi đã có ngữ nghĩa di chuyển. Đối với một lớp hỗ trợ di chuyển, di chuyển từ một đối tượng không chính thức kết thúc thời gian sống của nó - việc di chuyển được cho là để nó ở trạng thái "hợp lệ".
Trong việc thiết kế một lớp, có phải là một thực tiễn xấu nếu bạn thiết kế nó sao cho các bất biến của lớp chỉ được bảo toàn cho đến khi nó được di chuyển từ đó? Hoặc là ổn nếu nó sẽ cho phép bạn làm cho nó đi nhanh hơn.
Để làm cho nó cụ thể, giả sử tôi có một loại tài nguyên không thể sao chép nhưng có thể di chuyển như vậy:
class opaque {
opaque(const opaque &) = delete;
public:
opaque(opaque &&);
...
void mysterious();
void mysterious(int);
void mysterious(std::vector<std::string>);
};
Và vì bất kỳ lý do gì, tôi cần phải tạo một trình bao bọc có thể sao chép cho đối tượng này, để nó có thể được sử dụng, có lẽ trong một số hệ thống điều phối hiện có.
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { o_->mysterious(); }
void operator()(int i) { o_->mysterious(i); }
void operator()(std::vector<std::string> v) { o_->mysterious(v); }
};
Trong copyable_opaque
đối tượng này , một bất biến của lớp được thiết lập khi xây dựng là thành viên o_
luôn trỏ đến một đối tượng hợp lệ, vì không có ctor mặc định và ctor duy nhất không phải là ctor sao chép đảm bảo những điều này. Tất cả các operator()
phương pháp đều cho rằng bất biến này giữ và bảo tồn nó sau đó.
Tuy nhiên, nếu đối tượng được di chuyển từ đó, sau đó o_
sẽ trỏ đến không có gì. Và sau thời điểm đó, việc gọi bất kỳ phương thức nào operator()
sẽ gây ra sự cố UB / sự cố.
Nếu đối tượng không bao giờ được di chuyển từ đó, thì bất biến sẽ được bảo toàn ngay đến lệnh gọi dtor.
Giả sử rằng giả thuyết, tôi đã viết lớp này, và nhiều tháng sau, đồng nghiệp tưởng tượng của tôi đã trải qua UB bởi vì, trong một số chức năng phức tạp, trong đó có rất nhiều đối tượng bị xáo trộn vì một số lý do, anh ta đã chuyển từ một trong những điều này và sau đó được gọi là một trong những phương pháp của nó. Rõ ràng đó là lỗi của anh ấy vào cuối ngày, nhưng lớp học này có "được thiết kế kém?"
Suy nghĩ:
Nó thường là dạng xấu trong C ++ để tạo các đối tượng zombie phát nổ nếu bạn chạm vào chúng.
Nếu bạn không thể xây dựng một số đối tượng, không thể thiết lập các bất biến, sau đó ném ngoại lệ từ ctor. Nếu bạn không thể bảo toàn các bất biến trong một số phương thức, thì hãy báo hiệu lỗi bằng cách nào đó và quay lại. Điều này có nên khác đối với các đối tượng chuyển từ?Là đủ để chỉ tài liệu "sau khi đối tượng này đã được di chuyển từ, nó là bất hợp pháp (UB) để làm bất cứ điều gì với nó ngoài việc phá hủy nó" trong tiêu đề?
Có phải tốt hơn để liên tục khẳng định rằng nó là hợp lệ trong mỗi cuộc gọi phương thức?
Thích như vậy:
class copyable_opaque {
std::shared_ptr<opaque> o_;
copyable_opaque() = delete;
public:
explicit copyable_opaque(opaque _o)
: o_(std::make_shared<opaque>(std::move(_o)))
{}
void operator()() { assert(o_); o_->mysterious(); }
void operator()(int i) { assert(o_); o_->mysterious(i); }
void operator()(std::vector<std::string> v) { assert(o_); o_->mysterious(v); }
};
Các xác nhận không cải thiện đáng kể hành vi và chúng gây ra sự chậm lại. Nếu dự án của bạn sử dụng lược đồ "xây dựng phát hành / gỡ lỗi xây dựng", thay vì luôn luôn chạy với các xác nhận, tôi đoán điều này hấp dẫn hơn, vì bạn không trả tiền cho séc trong bản dựng phát hành. Nếu bạn không thực sự có các bản dựng gỡ lỗi, thì điều này có vẻ không hấp dẫn lắm.
- Là tốt hơn để làm cho lớp có thể sao chép, nhưng không di chuyển?
Điều này cũng có vẻ xấu và gây ra hiệu quả, nhưng nó giải quyết vấn đề "bất biến" một cách đơn giản.
Những gì bạn sẽ coi là "thực hành tốt nhất" có liên quan ở đây?