2015-05-04 19 views
8

Tôi chưa bao giờ sử dụng bất kỳ loại con trỏ thông minh nào, nhưng tôi tiếp tục đọc về chúng hầu như ở khắp mọi nơi khi chủ đề là con trỏ. Tôi hiểu rằng có những tình huống mà con trỏ thông minh đẹp hơn nhiều so với con trỏ thô, vì một số mở rộng chúng quản lý quyền sở hữu con trỏ. Tuy nhiên, tôi vẫn không biết, đó là dòng giữa "Tôi không cần con trỏ thông minh cho rằng" và "đây là một trường hợp cho con trỏ thông minh".Tại sao tôi muốn sử dụng một con trỏ thông minh trong tình huống này?

phép nói rằng, tôi có tình hình sau:

class A { 
public: 
    double get1(){return 1;} 
    double get2(){return 2;} 
}; 
class SomeUtilityClass { 
public: 
    SomeUtilityClass(A* a) : a(a) {} 
    double getResult(){return a->get1() + a->get2();} 
    void setA(A* a){a = a;} 
private: 
    A* a; 
}; 
int main(int argc, char** argv) { 
    A a; 
    SomeUtilityClass u(&a); 
    std::cout << u.getResult() << std::endl; 
    A a2; 
    u.setA(&a2); 
    std::cout << u.getResult() << std::endl; 
    return 0; 
} 

Đây là khóa học một ví dụ đơn giản đi. Ý tôi là, SomeUtilityClass không phải là "sở hữu" một thể hiện của A (vì nó chỉ là một lớp tiện ích), do đó nó chỉ giữ một con trỏ.

Liên quan đến con trỏ, điều duy nhất mà tôi nhận thức được rằng có thể đi sai là:

  • SomeUtilityClass có thể được khởi tạo với một con trỏ null
  • Các đối tượng được trỏ tới có thể bị xóa/đi ra khỏi phạm vi, mà không cần SomeUtilityClass để ý thấy nó

Làm thế nào một con trỏ thông minh có thể giúp tránh vấn đề này? Những lợi ích khác tôi sẽ nhận được bằng cách sử dụng một con trỏ thông minh trong trường hợp này?

PS: Tôi biết rằng có một số câu hỏi về con trỏ thông minh (ví dụ: this one). Tuy nhiên, tôi sẽ đánh giá cao, nếu bạn có thể cho tôi biết về tác động của ví dụ cụ thể này.

+7

Con trỏ thô hoàn toàn nắm bắt được khái niệm về tham chiếu không sở hữu đối với dữ liệu với thời gian tồn tại phạm vi. Bạn không nên sử dụng con trỏ thông minh ở đây. – Mankarse

+5

Nếu bạn đang sử dụng 'mới' trong tự nhiên, đó là một gợi ý khá mạnh, bạn có thể cần một con trỏ thông minh. Nếu bạn đang sử dụng 'new []', đó là một gợi ý khá mạnh, bạn có thể cần một 'std :: vector' hoặc một container khác. – JBL

+1

Nếu 'nullptr' không có ý nghĩa, bạn có thể xem xét tham chiếu hoặc' std :: reference_wrapper' thay thế. –

Trả lời

1

Theo mục đích của câu trả lời này tôi đang xác định lại Seta như:

void setA(A* new_a){a = new_a;} 

xem xét:

// Using your SomeUtilityClass 

int main() { 
    A a; 
    SomeUtilityClass u(&a); 
    // We define a new scope, just because: 
    { 
    A b; 
    u.setA(&b); 
    } 
    std::cout << u.getResult() << '\n'; 
    return 0; 
} 

Sau khi phạm vi xong, SomeUtilityClass có một con trỏ tòn ten và getResult() gọi Không xác định Hành vi. Lưu ý rằng điều này không thể được giải quyết với một tham chiếu: Bạn vẫn sẽ nhận được một lúng túng.

Bây giờ xem xét các phiên bản sử dụng một con trỏ thông minh:

class SomeUtilityClass { 
public: 
    SomeUtilityClass(std::shared_ptr<A>& a) : a{a} {} 
    double getResult(){return a->get1() + a->get2();} 
    void setA(std::shared_ptr<A>& new_a){a = new_a;} 
private: 
    std::shared_ptr<A> a; 
}; 

int main() { 
    std::shared_ptr<A> a{new A}; 
    SomeUtilityClass u{a}; 
    // We define a new scope, just because: 
    { 
    std::shared_ptr<A> b{new A}; 
    u.setA(b); 
    } 
    std::cout << u.getResult() << '\n'; 
    return 0; 
} 

Bởi vì bạn đã chia sẻ quyền sở hữu, không có cách nào để có được một con trỏ tòn ten. Bộ nhớ được chỉ định bởi b sẽ bị xóa như bình thường, nhưng chỉ sau khi u bị hủy (hoặc con trỏ của nó bị thay đổi).

IMHO, trong hầu hết các trường hợp, bạn nên sử dụng con trỏ thông minh (Ngay cả khi lúc đầu nó dường như không có ý nghĩa nhiều). Nó làm cho bảo trì dễ dàng hơn nhiều. Sử dụng con trỏ thô chỉ trong mã cụ thể mà thực sự cần chúng, và đóng gói/cô lập mã này càng nhiều càng tốt.

1

Nếu SomeUtilityClass không sở hữu biến thành viên a thì con trỏ thông minh sẽ không có ý nghĩa.

Bạn có thể xem xét một thành viên tham chiếu, điều này sẽ loại bỏ các vấn đề của con trỏ rỗng.

2

Điều này phụ thuộc vào cách tham số được tạo và lưu trữ. Nếu bạn không sở hữu bộ nhớ và nó có thể được phân bổ tĩnh hoặc động, một con trỏ thô là một giải pháp hoàn toàn hợp lý - đặc biệt nếu bạn cần hỗ trợ trao đổi dữ liệu như trong ví dụ của bạn. Một tùy chọn khác là sử dụng std::reference_wrapper, điều này sẽ loại bỏ vấn đề nullptr của bạn trong khi vẫn giữ cùng ngữ nghĩa.

Nếu bạn đang giữ con trỏ đến một số tài nguyên được chia sẻ (ví dụ: được lưu trữ trong một số std::shared_ptr ở đâu đó) và muốn kiểm tra xem nó đã bị xóa hay chưa, bạn có thể giữ std::weak_ptr.

1

Cách mặc định thể hiện con trỏ không sở hữu trong C++ là weak_ptr.Để sử dụng weak_ptr bạn cần phải sử dụng shared_ptr cho quyền sở hữu, vì vậy trong ví dụ của bạn, bạn sẽ sử dụng

shared_ptr<A> owner(...) 

thay vì

A a 

Sau đó là thành viên trỏ tin của bạn SomeUtilityClass bạn sử dụng con trỏ yếu:

weak_ptr<A> w; 

và khởi tạo nó bằng shared_ptr:

Tuy nhiên,
SomeUtilityClass(shared_ptr<A> o) : w(o) {} 

tuy nhiên, bạn không thể sử dụng trực tiếp weak_ptr, vì shared_ptr có thể hết phạm vi và con trỏ yếu của bạn không thể trỏ tới bất kỳ thứ gì. Trước khi sử dụng bạn cần phải khóa nó:

shared_ptr<A> locked = w.lock(); 

Con trỏ locked sẽ trống nếu con trỏ sở hữu không còn quản lý một đối tượng, kể từ ví dụ nó đã đi ra khỏi phạm vi. Nếu nó không rỗng, bạn có thể sử dụng nó và sau đó nó sẽ đi ra khỏi phạm vi tự động giải phóng khóa đối tượng.

Cả hai shared_ptrweak_ptr đều có sẵn trong thư viện chuẩn trong C++ 11 và trong Tăng cho trình biên dịch cũ hơn.

+1

'weak_ptr' không phải là cách mặc định để thể hiện không sở hữu.Nó chỉ được sử dụng để phá vỡ chu kỳ khi 'shared_ptr' được sử dụng. Cách mặc định để thể hiện không sở hữu là một con trỏ thô. –

+0

@SebastianRedl Bạn đã nêu ý kiến ​​cá nhân của mình vì nó thường được chấp nhận. Tuy nhiên, [cppreference] (http://en.cppreference.com/w/cpp/memory/weak_ptr) nói _std :: weak_ptr mô hình sở hữu tạm thời: khi một đối tượng cần được truy cập chỉ khi nó tồn tại_ và sau đó _Ngoài ra, std :: weak_ptr được sử dụng để phá vỡ tham chiếu vòng tròn, vì vậy ít nhất có - và ở nhiều nơi khác - ý kiến ​​khác nhau được thể hiện. –

+0

Và tôi đã sử dụng danh tiếng cá nhân của tôi để giảm bớt câu trả lời của bạn. Đây là cách SO hoạt động. Ngoài ra, làm thế nào để bạn đọc "mô hình sở hữu tạm thời" như hỗ trợ điểm được nêu của bạn "mặc định cách thể hiện con trỏ không sở hữu"? Nếu bất cứ điều gì, cppreference * mâu thuẫn * bài đăng của bạn và * hỗ trợ * nhận xét của tôi. –

0

Có nhiều loại con trỏ thông minh khác nhau. Trong trường hợp của bạn, rõ ràng là một con trỏ thông minh không thực sự cần thiết, nhưng nó vẫn có thể cung cấp một số lợi ích.

SomeUtilityClass có thể được khởi tạo với một con trỏ null

Cái này có lẽ là giải quyết tốt nhất với một tấm séc trong các nhà xây dựng, ném một ngoại lệ hoặc chỉ ra một lỗi trong một số cách khác trong trường hợp khi bạn nhận được một con trỏ NULL làm đối số. Tôi khó có thể tưởng tượng được một con trỏ thông minh sẽ giúp ích như thế nào, trừ khi bạn sử dụng một lớp con trỏ thông minh cụ thể mà không chấp nhận NULL, vì vậy nó sẽ kiểm tra cho bạn.

Đối tượng chỉ để có thể bị xóa/đi ra khỏi phạm vi, nếu không có sự SomeUtilityClass nhận thấy nó

Cái này thực sự có thể được giải quyết với một loại đặc biệt của con trỏ thông minh, nhưng sau đó nó là cần thiết rằng đối tượng được chỉ ra bằng cách nào đó hỗ trợ thông báo hủy diệt. Một ví dụ như vậy là lớp QPointer trong thư viện Qt, chỉ có thể trỏ đến QObject trường hợp, thông báo cho nó khi bị xóa, do đó con trỏ thông minh sẽ tự động trở thành NULL khi đối tượng bị xóa. Tuy nhiên, có một số vấn đề với cách tiếp cận này:

  1. Bạn cần kiểm tra NULL mỗi khi bạn truy cập đối tượng thông qua con trỏ thông minh.
  2. Nếu một điểm con trỏ thông minh để một thể hiện của một lớp học, nói MyClass, mở rộng lớp thực hiện thông báo xóa (QObject trong trường hợp Qt), bạn sẽ có được kết quả kỳ lạ: đó là destructor của QObject thông báo cho con trỏ thông minh , vì vậy có thể bạn truy cập nó khi destructor MyClass đã bắt đầu công việc bẩn thỉu của nó, do đó đối tượng bị hủy một phần, nhưng con trỏ không phải là NULL mà vì sự hủy diệt vẫn đang trong tiến trình.
+1

Bây giờ, tôi thường bỏ qua downvotes, nhưng trong trường hợp này tôi đã trả lời rõ ràng các điểm được chỉ định bởi OP, cung cấp một ví dụ từ một thư viện C++ được sử dụng rộng rãi. Điều gì trên trái đất là sai lầm như vậy với câu trả lời của tôi rằng ai đó đã hy sinh toàn bộ một điểm danh tiếng chỉ để nói cho thế giới biết nó nguy hiểm đến mức nào? Có lẽ bạn nói với tôi và tôi sửa chữa nó? –

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