Lưu ý: sau đây là mã C ++ 03, nhưng chúng tôi hy vọng sẽ chuyển sang C ++ 11 trong hai năm tới, vì vậy chúng tôi phải ghi nhớ điều đó.
Tôi đang viết một hướng dẫn (cho người mới, trong số những người khác) về cách viết giao diện trừu tượng trong C ++. Tôi đã đọc cả hai bài viết của Sutter về chủ đề này, tìm kiếm trên internet các ví dụ và câu trả lời, và thực hiện một số bài kiểm tra.
Mã này KHÔNG được biên dịch!
void foo(SomeInterface & a, SomeInterface & b)
{
SomeInterface c ; // must not be default-constructible
SomeInterface d(a); // must not be copy-constructible
a = b ; // must not be assignable
}
Tất cả các hành vi trên đều tìm ra nguồn gốc của vấn đề của chúng khi cắt : Giao diện trừu tượng (hoặc lớp không có lá trong cấu trúc phân cấp) không nên có thể xây dựng cũng như không thể chuyển đổi / gán được, NGAY nếu lớp dẫn xuất có thể.
Giải pháp thứ 0: giao diện cơ bản
class VirtuallyDestructible
{
public :
virtual ~VirtuallyDestructible() {}
} ;
Giải pháp này rất đơn giản và hơi ngây thơ: Nó không thực hiện được tất cả các ràng buộc của chúng tôi: Nó có thể được xây dựng mặc định, xây dựng sao chép và gán sao chép (Tôi thậm chí không chắc chắn về các nhà xây dựng di chuyển và chuyển nhượng, nhưng tôi vẫn còn 2 năm để hình dung nó ra).
- Chúng tôi không thể khai báo công cụ ảo thuần túy bởi vì chúng tôi cần giữ cho nó nội tuyến và một số trình biên dịch của chúng tôi sẽ không tiêu hóa các phương thức ảo thuần túy với phần thân trống nội tuyến.
- Vâng, điểm duy nhất của lớp này là làm cho những người thực hiện hầu như bị phá hủy, đó là một trường hợp hiếm gặp.
- Ngay cả khi chúng ta có một phương thức thuần ảo bổ sung (phần lớn các trường hợp), lớp này vẫn có thể được sao chép.
Vì vậy, không ...
Giải pháp thứ nhất: boost :: không thể quét
class VirtuallyDestructible : boost::noncopyable
{
public :
virtual ~VirtuallyDestructible() {}
} ;
Giải pháp này là tốt nhất, vì nó đơn giản, rõ ràng và C ++ (không có macro)
Vấn đề là nó vẫn không hoạt động cho giao diện cụ thể đó bởi vì VirtualConstructible vẫn có thể được xây dựng mặc định .
- Chúng tôi không thể khai báo công cụ ảo thuần vì chúng tôi cần giữ nội tuyến và một số trình biên dịch của chúng tôi sẽ không tiêu hóa được.
- Vâng, điểm duy nhất của lớp này là làm cho những người thực hiện hầu như bị phá hủy, đó là một trường hợp hiếm gặp.
Một vấn đề khác là các lớp thực hiện giao diện không thể sao chép sau đó phải khai báo / xác định rõ ràng hàm tạo sao chép và toán tử gán nếu chúng cần có các phương thức đó (và trong mã của chúng tôi, chúng tôi có các lớp giá trị vẫn có thể được truy cập bởi khách hàng của chúng tôi thông qua giao diện).
Điều này đi ngược lại Quy tắc không, đó là nơi chúng ta muốn đến: Nếu việc triển khai mặc định là ổn, thì chúng ta sẽ có thể sử dụng nó.
Giải pháp thứ 2: làm cho chúng được bảo vệ!
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
// With C++11, these methods would be "= default"
MyInterface() {}
MyInterface(const MyInterface & ) {}
MyInterface & operator = (const MyInterface & ) { return *this ; }
} ;
Mẫu này tuân theo các ràng buộc kỹ thuật mà chúng tôi đã có (ít nhất là trong mã người dùng): MyInterface không thể được xây dựng mặc định, không thể được xây dựng sao chép và không thể được gán bản sao.
Ngoài ra, nó không áp đặt các ràng buộc giả tạo nào trong việc triển khai các lớp , sau đó có thể tự do tuân theo Quy tắc 0 hoặc thậm chí khai báo một số hàm tạo / toán tử là "= default" trong C ++ 11/14 mà không gặp vấn đề gì.
Bây giờ, điều này khá dài dòng và một giải pháp thay thế sẽ là sử dụng macro, đại loại như:
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;
Việc bảo vệ phải nằm ngoài macro (vì nó không có phạm vi).
Chính xác là "không gian tên" (nghĩa là tiền tố với tên của công ty hoặc sản phẩm của bạn), macro sẽ vô hại.
Và lợi thế là mã được bao gồm trong một nguồn, thay vì được sao chép dán trong tất cả các giao diện. Nếu công cụ xây dựng di chuyển và chuyển nhượng di chuyển bị vô hiệu hóa rõ ràng theo cùng một cách trong tương lai, đây sẽ là một thay đổi rất nhẹ trong mã.
Phần kết luận
- Tôi có bị hoang tưởng muốn mã được bảo vệ chống cắt trong giao diện không? (Tôi tin là tôi không, nhưng người ta không bao giờ biết ...)
- Giải pháp tốt nhất trong số những người ở trên là gì?
- Có cách nào khác, giải pháp tốt hơn?
Xin nhớ rằng đây là một mô hình sẽ đóng vai trò là kim chỉ nam cho người mới (trong số những người khác), vì vậy một giải pháp như: "Mỗi trường hợp nên thực hiện nó" không phải là một giải pháp khả thi.
Tiền thưởng và kết quả
Tôi đã trao tiền thưởng cho coredump vì thời gian dành cho việc trả lời các câu hỏi và sự liên quan của các câu trả lời.
Giải pháp của tôi cho vấn đề có thể sẽ đi đến một cái gì đó như thế:
class MyInterface
{
DECLARE_CLASS_AS_INTERFACE(MyInterface) ;
public :
// the virtual methods
} ;
... với macro sau:
#define DECLARE_CLASS_AS_INTERFACE(ClassName) \
public : \
virtual ~ClassName() {} \
protected : \
ClassName() {} \
ClassName(const ClassName & ) {} \
ClassName & operator = (const ClassName & ) { return *this ; } \
private :
Đây là một giải pháp khả thi cho vấn đề của tôi vì những lý do sau:
- Lớp này không thể được khởi tạo (các hàm tạo được bảo vệ)
- Lớp này hầu như có thể bị phá hủy
- Lớp này có thể được kế thừa mà không áp đặt các ràng buộc không đáng có đối với các lớp kế thừa (ví dụ: lớp kế thừa có thể được mặc định là có thể sao chép được)
- Việc sử dụng macro có nghĩa là "khai báo" giao diện có thể dễ dàng nhận ra (và có thể tìm kiếm) và mã của nó được đặt ở một nơi giúp dễ dàng sửa đổi hơn (một tên tiền tố phù hợp sẽ loại bỏ xung đột tên không mong muốn)
Lưu ý rằng các câu trả lời khác đã đưa ra cái nhìn sâu sắc có giá trị. Cảm ơn tất cả các bạn đã cho nó một shot.
Lưu ý rằng tôi đoán tôi vẫn có thể đặt một tiền thưởng khác cho câu hỏi này và tôi đánh giá cao những câu trả lời khai sáng đủ để tôi thấy một câu hỏi, tôi sẽ mở một tiền thưởng chỉ để gán câu trả lời đó.
virtual ~VirtuallyDestructible() = 0
và kế thừa ảo các lớp giao diện (chỉ với các thành viên trừu tượng). Bạn có thể bỏ qua VirtualDestestable, có khả năng.
virtual void bar() = 0;
ví dụ? Điều đó sẽ ngăn giao diện của bạn không bị ảnh hưởng.