2010-07-22 28 views
5

Tôi đang làm việc trong một cơ sở mã C++ kế thừa rất lớn mà sẽ không còn tên. Là một cơ sở mã kế thừa, nó chuyển các con trỏ thô xung quanh khắp nơi. Nhưng chúng tôi đang dần cố gắng hiện đại hóa nó và vì vậy cũng có một số mẫu con trỏ thông minh. Những con trỏ thông minh (không giống như, nói là, scoped_ptr của Boost) có một chuyển đổi ẩn thành con trỏ thô, để bạn có thể chuyển một trong số chúng thành một thường trình có con trỏ thô mà không cần phải viết .get(). Một nhược điểm lớn của điều này là bạn cũng có thể vô tình sử dụng một trong một tuyên bố delete, và sau đó bạn có một lỗi đôi miễn phí, mà có thể là một nỗi đau thực sự để theo dõi.Mẫu "con trỏ thông minh" C++ tự động chuyển đổi thành con trỏ trần nhưng không thể xóa một cách rõ ràng

Có cách nào để sửa đổi mẫu sao cho mẫu vẫn có chuyển đổi ẩn thành con trỏ thô, nhưng gây ra lỗi biên dịch nếu được sử dụng trong câu lệnh xóa? Như thế này:

#include <my_scoped_ptr> 

struct A {}; 
extern void f(A*); 

struct B 
{ 
    scoped_ptr<A> a; 

    B(); 
    ~B(); 
}; 

B::B() 
    : a(new A) 
{ 
    f(a); // this should compile 
} 

B::~B() 
{ 
    delete a; // this should NOT compile 
} 

Trả lời

7

Tiêu chuẩn nói

Các toán hạng phải có một loại con trỏ, hoặc một loại lớp có một hàm chuyển đổi duy nhất (12.3.2) cho một loại con trỏ. Nếu toán hạng có loại lớp, toán hạng được chuyển thành kiểu con trỏ bằng cách gọi hàm chuyển đổi được đề cập ở trên và toán hạng được chuyển đổi được sử dụng thay cho toán hạng gốc cho phần còn lại của phần này.

Bạn có thể (ab) - sử dụng sự vắng mặt của độ phân giải quá tải bằng cách khai báo phiên bản const của hàm chuyển đổi. Trên một trình biên dịch phù hợp đó là đủ để làm cho nó không hoạt động nữa với delete:

struct A { 
    operator int*() { return 0; } 
    operator int*() const { return 0; } 
}; 

int main() { 
    A a; 
    int *p = a; // works 
    delete a; // doesn't work 
} 

Kết quả trong những điều sau

[[email protected] cpp]$ clang++ main1.cpp 
main1.cpp:9:3: error: ambiguous conversion of delete expression of type 'A' to a pointer 
    delete a; // doesn't work 
^ ~ 
main1.cpp:2:3: note: candidate function    
    operator int*() { return 0; } 
^
main1.cpp:3:3: note: candidate function    
    operator int*() const { return 0; } 
^
1 error generated. 

trên trình biên dịch được ít phù hợp trong vấn đề đó (EDG/Comeau, GCC) bạn có thể làm cho chức năng chuyển đổi thành một mẫu. delete không mong đợi một loại đặc biệt, vì vậy đây sẽ làm việc:

template<typename T> 
operator T*() { return /* ... */ } 

Tuy nhiên, điều này có nhược điểm là smartpointer của bạn bây giờ là chuyển đổi thành bất kỳ con trỏ-type. Mặc dù chuyển đổi thực tế vẫn được đánh máy, nhưng điều này sẽ không loại trừ các chuyển đổi ở phía trước mà thay vào đó sẽ cung cấp một lỗi thời gian biên dịch sau này. Đáng buồn thay, SFINAE dường như không thể với chức năng chuyển đổi trong C++ 03 :) Một cách khác nhau là để trả về một con trỏ lồng nhau kiểu tin từ các chức năng khác

struct A { 
    operator int*() { return 0; } 

private: 
    struct nested { }; 
    operator nested*() { return 0; } 
}; 

Vấn đề duy nhất là bây giờ với một chuyển đổi void*, trong trường hợp cả hai hàm chuyển đổi đều có khả năng tương đương nhau. Một công việc được đề xuất bởi @Luther là trả về một kiểu con trỏ hàm từ hàm chuyển đổi khác, hoạt động với cả GCC và Comeau và loại bỏ vấn đề void* trong khi không có vấn đề gì khác trên đường dẫn chuyển đổi thông thường, không giống như giải pháp mẫu

struct A { 
    operator int*() { return 0; } 

private: 
    typedef void fty(); 
    operator fty*() { return 0; } 
}; 

Lưu ý rằng các giải pháp này chỉ cần cho các trình biên dịch không phù hợp.

+0

Bạn sẽ làm điều đó, đúng không. – GManNickG

+0

g ++ chưa được quyết định, 4.4 không biên dịch, 4.5. Nếu tôi thay đổi loại chuyển đổi thứ hai thành kiểu con trỏ hàm, thì g ++ sẽ chuyển nó thành đúng và dừng lại ở 'delete a'. –

+0

@ Xin cảm ơn, đã thêm :) –

1

Bạn có thể sử dụng một kỹ thuật được trình bày bởi Boost, nhưng mối quan tâm của tôi là bạn đang cho phép chuyển đổi tiềm ẩn từ một con trỏ thông minh để một con trỏ nguyên, thường được tán thành trên. Bên cạnh đó, người dùng có thể gọi delete trên một con trỏ thu được bởi toán tử ->, vì vậy bạn không thể làm gì để ngăn chặn một kẻ ngốc xác định làm việc xung quanh bất kỳ cơ chế nào bạn đưa ra.

Bạn thực sự chỉ nên triển khai phương thức get() thay vì cung cấp operator T*() để ít nhất cuộc gọi đến delete smartptr sẽ không biên dịch. Những kẻ không ngốc sẽ có thể nhận ra rằng có lẽ đó là lý do tại sao điều đó không hiệu quả.

Có, nhiều việc phải loại ra LegacyFunc(smartptr.get()) hơn LegacyFunc(smartptr), nhưng trước đây được ưu tiên vì nó làm rõ và ngăn các chuyển đổi không mong muốn xảy ra, như delete smartptr.

gì nếu bạn có chức năng như thế này:

void LegacyOwnPointer(SomeType* ptr); 

nơi hàm sẽ lưu trữ các con trỏ nơi nào đó? Điều này sẽ vít lên con trỏ thông minh, bởi vì bây giờ nó không nhận thức được rằng cái gì khác đang sở hữu con trỏ thô.

Dù bằng cách nào, bạn có một số việc phải làm. Con trỏ thông minh giống như con trỏ thô, nhưng chúng không giống nhau, vì vậy bạn không thể chỉ tìm và thay thế tất cả các phiên bản T* và thay thế bằng my_scoped_ptr<T> và mong đợi nó hoạt động giống như trước đây.

+0

Đó là một vấn đề khác. – GManNickG

+0

Dù có bị cau mày hay không, nó sẽ là vô vọng để cố gắng đánh lừa con trỏ thông minh vào cơ sở mã kế thừa này mà không cần chuyển đổi ẩn thành con trỏ thô. Có quá nhiều nơi bạn cần cung cấp con trỏ thô. - Đề nghị của bạn chỉ giúp ích nếu tôi có thể sửa đổi lớp được chỉ định, mà tôi không thể nói chung. Tôi cần một kỹ thuật hoạt động hoàn toàn trong lớp con trỏ thông minh. – zwol

+1

Tôi thực sự không nghĩ rằng bạn đánh giá cao thực tế làm việc trong một cơ sở mã 3 năm, 15 tuổi. Nếu tôi lấy chuyển đổi ngầm ra khỏi mẫu con trỏ thông minh (vốn đã được sử dụng nhiều), tôi sẽ phải đặt trong * hàng nghìn * các cuộc gọi .get() và đồng nghiệp của tôi sẽ lynch tôi. – zwol

4

Không có cách nào để ngăn chặn và không phải cách khác. Bất cứ nơi nào nó có thể được chuyển đổi hoàn toàn thành một con trỏ cho một cuộc gọi hàm, nó có thể được chuyển đổi hoàn toàn cho một biểu thức xóa.

Đặt cược tốt nhất của bạn là xóa hàm chuyển đổi. Tình huống của bạn chính xác là lý do tại sao các toán tử chuyển đổi do người dùng xác định là nguy hiểm và không nên được sử dụng thường xuyên.


I'm wrong. :(

+0

Xem câu trả lời của tôi cho "Trong silicio". Tôi nhận thức được các lập luận chống lại các chuyển đổi tiềm ẩn, nhưng nó đơn giản là không thực tế nếu không có chúng trong bối cảnh này. – zwol

+0

@Zack: Sau đó, bạn sẽ không phải xóa chúng. Đó là lựa chọn của bạn: Xóa chuyển đổi ngầm định và sử dụng 'get()' rõ ràng cho các hàm, hoặc để lại các chuyển đổi tiềm ẩn và hy vọng xóa không được gọi trên đó. – GManNickG

+0

Tôi lo sợ bạn đúng, nhưng tôi sẽ để câu hỏi mở trong một ngày hoặc lâu hơn trong trường hợp ai đó đến với một cái gì đó thông minh. – zwol

0

chưa nghĩ nhiều về điều này nhưng ... Bạn có thể cung cấp quá tải cho thao tác xóa được đánh máy mạnh cho các phiên bản của lớp mẫu sao cho khi mã được bao gồm biên dịch không thành công? nếu điều này là trong tập tin tiêu đề của bạn sau đó chuyển đổi tiềm ẩn trong các cuộc gọi để xóa nên được ngăn chặn trong lợi của một cuộc gọi đến quá tải của bạn.

operator delete (my_scoped_ptr) {// ... Mã uncompilable goes here }

Xin lỗi nếu điều này hóa ra lại là một ý tưởng ngu ngốc.

+0

Không, điều đó sẽ được gọi cho các biểu thức "xóa con trỏ đến con trỏ thông minh", nhưng không phải để "xóa con trỏ thông minh" đang gây ra sự cố. –

0

Tôi có thể thấy nơi bạn không muốn làm một ứng dụng khổng lồ .get() 's. Bạn đã bao giờ xem xét một sự thay thế nhỏ hơn nhiều của xóa?

struct A 
{ 
    friend static void Delete(A* p) { delete p; } 

private: 
    ~A(){} 
}; 

struct B 
{ 
}; 

int main() 
    { 

    delete new B(); //ok 

    Delete(new A); //ok 

    delete new A; //compiler error 

    return (0); 
    } 
+0

Điều này chỉ giúp ích nếu tôi có thể sửa đổi lớp được trỏ đến, mà tôi không thể nói chung. – zwol

Các vấn đề liên quan