2011-01-26 27 views
19

Để giải quyết một vấn đề rất đặc biệt trong ứng dụng của tôi, tôi cần một con trỏ được chia sẻ để phân bổ dữ liệu, nhưng với thế giới bên ngoài, kiểu dữ liệu cơ bản sẽ vẫn ẩn.Con trỏ void chia sẻ. Tại sao điều này hoạt động?

tôi có thể giải quyết việc này bằng cách làm cho một số loại lớp gốc trong đó tất cả các lớp học khác của tôi kế thừa, và sử dụng một shared_ptr trên lớp gốc này, như thế này:

std::shared_ptr<Root> 

Tuy nhiên:

  • Tôi không muốn tất cả các lớp của tôi thừa kế từ lớp Gốc này chỉ để có thể có con trỏ được chia sẻ này
  • Đôi khi tôi muốn trả về con trỏ được chia sẻ thành std :: vector hoặc std :: list hoặc std :: thiết lập, ... mà rõ ràng là không kế thừa từ lớp gốc của tôi

Strange đủ, có vẻ như bạn có thể tạo một shared_ptr vào khoảng trống và điều này dường như làm việc một cách chính xác, như thể hiện trong ví dụ này:

class X 
    { 
    public: 
     X() {std::cout << "X::ctor" << std::endl;} 
     virtual ~X() {std::cout << "X::dtor" << std::endl;} 
    }; 

typedef std::shared_ptr<void> SharedVoidPointer; 

int main() 
{ 
X *x = new X(); 
SharedVoidPointer sp1(x); 
} 

x sẽ bị xóa một cách chính xác và trong một thí nghiệm lớn hơn tôi có thể xác minh rằng con trỏ chia sẻ thực sự là những gì nó cần làm (xóa x afer shared_ptr cuối cùng biến ra ánh sáng).

Tất nhiên điều này giải quyết được vấn đề của tôi, vì bây giờ tôi có thể trả về dữ liệu với một thành viên dữ liệu SharedVoidPointer và chắc chắn rằng nó được dọn dẹp đúng nơi cần thiết.

Nhưng điều này có được đảm bảo hoạt động trong mọi trường hợp không? Nó hoạt động rõ ràng trong Visual Studio 2010, nhưng điều này cũng hoạt động chính xác trên các trình biên dịch khác? Trên các nền tảng khác?

+1

bản sao có thể có của [cách tăng :: ~ shared_ptr hoạt động?] (Http://stackoverflow.com/questions/4560372/how-boostshared-ptr-works) –

+0

@Charles, Câu hỏi có vẻ tương tự, nhưng trong câu hỏi của tôi Tôi rõ ràng yêu cầu tránh void-con trỏ (không trỏ đến một lớp cơ sở), và cho dù điều này được đảm bảo bởi các tiêu chuẩn (không phải lý do tại sao nó hoạt động trong Tăng). – Patrick

+1

@Patrick: Đó chính là vấn đề tương tự. Deleter được xây dựng khi con trỏ thông minh có quyền sở hữu con trỏ tại thời điểm mà thông tin kiểu tĩnh của con trỏ mà bạn truyền vào được sử dụng để tạo ra deleter. –

Trả lời

26

Các constructor shared_ptr mà bạn sử dụng thực sự là một mẫu nhà xây dựng mà trông giống như:

template <typename U> 
shared_ptr(U* p) { } 

Nó biết bên trong của các nhà xây dựng những gì các loại thực tế của con trỏ là (X) và sử dụng thông tin này để tạo ra một functor có thể chính xác delete con trỏ và đảm bảo destructor đúng được gọi. Functor này (được gọi là "deleter" của shared_ptr) thường được lưu trữ cùng với số lượng tham chiếu được sử dụng để duy trì quyền sở hữu chung của đối tượng.

Lưu ý rằng thao tác này chỉ hoạt động nếu bạn chuyển con trỏ của loại chính xác đến hàm tạo shared_ptr. Nếu bạn đã thay cho biết:

SharedVoidPointer sp1(static_cast<void*>(x)); 

thì đây sẽ không làm việc vì trong mẫu nhà xây dựng, U sẽ void, không X. Hành vi sau đó sẽ không được xác định, bởi vì bạn không được phép gọi delete với một con trỏ void.

Nói chung, bạn được an toàn nếu bạn luôn gọi new trong việc xây dựng một shared_ptr và không tách rời việc tạo ra các đối tượng (new) từ việc lấy quyền sở hữu của đối tượng (sự sáng tạo của các shared_ptr) .

+0

Điều này có đảm bảo hoạt động theo cách này theo tiêu chuẩn C++ 0x không? Hoặc là điều này chỉ vì nó được thực hiện theo cách này bởi Microsoft? – Patrick

+1

@Patrick: Nó được đảm bảo trong C++ 0x (và trong C++ TR1 và trong Boost). –

+0

Và bạn thậm chí có thể vượt qua một chức năng đóng tùy chỉnh nếu bạn đang làm việc với HANDLE hoặc FILEs được gọi khi đối tượng của bạn bị hủy. – RedX

10

Tôi nghĩ điểm ẩn của câu hỏi là bạn không thể xóa bởi void*, do đó, có vẻ lạ mà bạn có thể xóa qua shared_ptr<void>.

Bạn không thể xóa một đối tượng thông qua số void* thô chủ yếu bởi vì nó sẽ không biết những gì destructor để gọi. Sử dụng destructor ảo không giúp đỡ vì void không có vtable (và do đó không có destructor ảo).

James McNellis giải thích rõ ràng lý do tại sao shared_ptr<void> công trình, nhưng có cái gì khác thú vị ở đây: Giả sử bạn làm theo các documented best practice luôn luôn sử dụng các hình thức sau khi gọi new ...

shared_ptr<T> p(new Y); 

... không cần thiết phải có một destructor ảo khi sử dụng shared_ptr. Điều này đúng cho dù Tvoid hoặc trong trường hợp quen thuộc hơn là T là cơ sở đa hình của Y.

này đi ngược lại một sự khôn ngoan thông thường từ lâu: That interface classes MUST have virtual destructors.

mối quan tâm của OP delete (void*) được giải quyết bởi thực tế là các nhà xây dựng shared_ptr là một mẫu nhớ kiểu dữ liệu mà nó cần phải hủy. Cơ chế này giải quyết vấn đề hủy hoại ảo theo cách giống hệt nhau.

Vì vậy, mặc dù các loại thực tế của đối tượng không nhất thiết phải bắt trong các loại shared_ptr bản thân (vì T không phải là loại giống như Y), tuy nhiên, các shared_ptr nhớ loại của đối tượng mà nó đang giữ và nó thực hiện một phép đúc cho kiểu đó (hoặc thực hiện một cái gì đó tương đương với điều đó) khi nói đến thời gian để xóa đối tượng.

+0

+1 Điều cần biết. –

+0

Lưu ý: Việc thực hiện trong Boost thực sự ghi nhớ một con trỏ đến cả hai loại (** T ** và ** Y **). Nó cung cấp cho bạn 'T *' cho hoạt động bình thường, chẳng hạn như 'T * get()', nhưng sử dụng 'Y *' khi đến lúc xóa đối tượng. – nobar

+0

Câu trả lời sau đây cung cấp ngữ cảnh bổ sung xung quanh thực tế là shared_pointer giữ hai con trỏ có thể có các loại khác nhau (và thậm chí cả các giá trị khác nhau): http://stackoverflow.com/questions/6826402/why-is-the-size-of- make-shared-two-pointers/6830836 # 6830836 – nobar

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