Tôi không thể nghĩ về một tiêu đề câu hỏi thích hợp để mô tả sự cố. Hy vọng rằng các chi tiết dưới đây giải thích rõ ràng vấn đề của tôi.Ngăn người dùng phát sinh từ cơ sở CRTP không chính xác
Xét đoạn mã sau
#include <iostream>
template <typename Derived>
class Base
{
public :
void call()
{
static_cast<Derived *>(this)->call_impl();
}
};
class D1 : public Base<D1>
{
public :
void call_impl()
{
data_ = 100;
std::cout << data_ << std::endl;
}
private :
int data_;
};
class D2 : public Base<D1> // This is wrong by intension
{
public :
void call_impl()
{
std::cout << data_ << std::endl;
}
private :
int data_;
};
int main()
{
D2 d2;
d2.call_impl();
d2.call();
d2.call_impl();
}
Nó sẽ biên dịch và chạy mặc dù định nghĩa của D2
là cố ý sai. Cuộc gọi đầu tiên d2.call_impl()
sẽ xuất ra một số bit ngẫu nhiên được mong đợi là D2::data_
không được khởi tạo. Các cuộc gọi thứ hai và thứ ba tất cả sẽ xuất ra 100
cho số data_
.
Tôi hiểu lý do tại sao nó sẽ biên dịch và chạy, sửa tôi nếu tôi sai.
Khi chúng tôi thực hiện cuộc gọi d2.call()
, cuộc gọi đã được giải quyết để Base<D1>::call
, và điều đó sẽ đúc this
để D1
và gọi D1::call_impl
. Bởi vì D1
thực sự là hình thức có nguồn gốc Base<D1>
, do đó, diễn viên là tốt tại thời gian biên dịch.
Vào lúc chạy, sau khi các diễn viên, this
, trong khi nó đang thực sự là một đối tượng D2
được xử lý như thể nó là D1
, và cuộc gọi đến D1::call_impl
sẽ sửa đổi các bit nhớ được nghĩa vụ phải được D1::data_
, và đầu ra. Trong trường hợp này, các bit này xảy ra ở nơi D2::data_
. Tôi nghĩ rằng d2.call_impl()
thứ hai cũng sẽ là hành vi không xác định tùy thuộc vào việc thực hiện C++.
Vấn đề là, mã này, trong khi sai về cơ bản, sẽ không có dấu hiệu lỗi cho người dùng. Những gì tôi đang thực sự làm trong dự án của tôi là tôi có một lớp cơ sở CRTP hoạt động như một công cụ điều phối. Một lớp khác trong thư viện truy cập giao diện lớp cơ sở CRTP, nói call
và call
sẽ gửi đến call_dispatch
có thể là lớp thực thi mặc định cơ sở hoặc triển khai lớp dẫn xuất. Tất cả những điều này sẽ hoạt động tốt nếu người dùng định nghĩa lớp dẫn xuất, nói D
, thực sự bắt nguồn từ Base<D>
. Nó sẽ tăng lỗi thời gian biên dịch nếu nó có nguồn gốc từ Base<Unrelated>
trong đó Unrelated
không được lấy từ Base<Unrelated>
. Nhưng nó sẽ không ngăn người dùng viết mã như trên.
Người dùng sử dụng thư viện này bằng cách lấy từ lớp cơ sở CRTP và cung cấp một số chi tiết triển khai. Chắc chắn có những lựa chọn thay thế thiết kế khác có thể tránh được vấn đề sử dụng không chính xác như trên (ví dụ một lớp cơ sở trừu tượng). Nhưng bây giờ hãy để chúng sang một bên và chỉ tin rằng tôi cần thiết kế này vì một lý do nào đó.
Vì vậy, câu hỏi của tôi là, có cách nào để tôi có thể ngăn người dùng viết lớp không chính xác có nguồn gốc như xem ở trên không. Tức là, nếu người dùng viết một lớp triển khai có nguồn gốc, hãy nói D
, nhưng anh ta lấy nó từ Base<OtherD>
, thì lỗi thời gian biên dịch sẽ được nâng lên.
Một giải pháp là sử dụng dynamic_cast
. Tuy nhiên, đó là mở rộng và ngay cả khi nó hoạt động nó là một lỗi thời gian chạy.
Câu trả lời ngắn gọn: không. 'dynamic_cast' có thể tốn kém, nhưng nó rẻ hơn so với cố gắng sửa người dùng của bạn. – Rook
Ít nhất dynamic_cast thất bại sẽ được tìm thấy nhanh hơn trong các bài kiểm tra đơn vị. Bạn không thể có trình biên dịch bảo vệ bạn khỏi mọi lỗi có thể, như viết 'i + 1' khi có nghĩa là 'i - 1'. Điều này là tương tự. –
bản sao có thể có của [Cách tránh lỗi trong khi sử dụng CRTP?] (Http://stackoverflow.com/questions/4417782/how-to-avoid-errors-while-using-crtp) –