2012-01-03 28 views
47

Tôi đã viết một phương thức nhà máy tĩnh trả về một đối tượng Foobar mới được điền từ một đối tượng dữ liệu khác. Gần đây tôi đã bị ám ảnh bởi ngữ nghĩa quyền sở hữu và tôi tự hỏi liệu tôi có đang truyền đạt thông điệp đúng hay không bằng cách phương thức này trả lại một unique_ptr.Thực hành không tốt để trả về unique_ptr cho con trỏ thô như ngữ nghĩa quyền sở hữu?

class Foobar { 
public: 
    static unique_ptr<Foobar> factory(DataObject data); 
} 

Mục đích của tôi là cho mã khách hàng biết rằng họ sở hữu con trỏ. Không có con trỏ thông minh, tôi chỉ đơn giản trả về Foobar*. Tuy nhiên, tôi muốn thực thi bộ nhớ này bị xóa để tránh các lỗi tiềm ẩn, do đó, unique_ptr có vẻ như là một giải pháp thích hợp. Nếu khách hàng muốn kéo dài tuổi thọ của con trỏ, họ chỉ cần gọi .release() sau khi họ nhận được unique_ptr.

Foobar* myFoo = Foobar::factory(data).release(); 

Câu hỏi của tôi có hai phần:

  1. Liệu phương pháp này truyền đạt ngữ nghĩa sở hữu có đúng không?
  2. Đây có phải là "thực hành không tốt" để trả lại unique_ptr thay vì một con trỏ thô không?
+0

Bạn đang quay trở lại unqiue_ptr để nói với khách hàng rằng họ sở hữu con trỏ? Điều này hoàn toàn trái ngược với những gì tôi mong đợi (vì họ phải nắm rõ quyền sở hữu con trỏ duy nhất). – jknupp

+1

Thay vào đó, bạn có thể muốn sử dụng ngữ nghĩa di chuyển (nếu bạn có thể sử dụng C++ 11). Với điều này, người dùng có thể quyết định cách kéo dài tuổi thọ của đối tượng được tạo bởi nhà máy. – evnu

+1

@evnu đó là điều gì đó được thực hiện tự động cho bạn, phải không? –

Trả lời

58

Trả lại std::unique_ptr từ phương pháp nhà máy là tốt và phải là phương pháp được khuyến nghị. Thông báo chuyển tải là (IMO): Bạn hiện là chủ sở hữu duy nhất của đối tượng này. Hơn nữa, để thuận tiện cho bạn, đối tượng biết cách tiêu diệt chính nó.

Tôi nghĩ rằng điều này tốt hơn nhiều sau đó trả về một con trỏ thô (nơi máy khách phải ghi nhớ cách thức và nếu loại bỏ con trỏ này).

Tuy nhiên tôi không hiểu nhận xét của bạn về việc giải phóng con trỏ để kéo dài tuổi thọ của nó. Nói chung tôi hiếm khi thấy bất kỳ lý do nào để gọi release trên một con trỏ thông minh, vì tôi nghĩ con trỏ nên luôn được quản lý bởi một số cấu trúc RAII (chỉ về tình huống duy nhất mà tôi gọi là release là đặt con trỏ vào một cơ sở hạ tầng quản lý khác, ví dụ một unique_ptr với một deleter khác nhau, sau khi tôi đã làm một cái gì đó để đảm bảo dọn dẹp bổ sung).

Vì vậy khách hàng có thể (và nên) chỉ đơn giản là lưu trữ các unique_ptr ở đâu đó (chẳng hạn như unique_ptr khác, mà đã được di chuyển xây dựng từ trở lại một) chừng nào họ cần đối tượng (hoặc một shared_ptr, nếu họ cần nhiều bản sao của con trỏ).Vì vậy, các mã clientside nên trông như thế này:

std::unique_ptr<FooBar> myFoo = Foobar::factory(data); 
//or: 
std::shared_ptr<FooBar> myFoo = Foobar::factory(data); 

Cá nhân tôi cũng sẽ thêm một typedef cho các loại con trỏ trả lại (trong trường hợp này std::unique_ptr<Foobar>) và hoặc deleter sử dụng (trong trường hợp này std :: default_deleter) để đối tượng nhà máy của bạn. Điều đó làm cho nó dễ dàng hơn nếu sau này bạn quyết định thay đổi phân bổ con trỏ của bạn (và do đó cần một phương pháp khác để hủy con trỏ, sẽ được hiển thị như tham số mẫu thứ hai của std::unique_ptr). Vì vậy, tôi sẽ làm một cái gì đó như thế này:

class Foobar { 
public: 
    typedef std::default_deleter<Foobar>  deleter; 
    typedef std::unique_ptr<Foobar, deleter> unique_ptr; 

    static unique_ptr factory(DataObject data); 
} 

Foobar::unique_ptr myFoo = Foobar::factory(data); 
//or: 
std::shared_ptr<Foobar> myFoo = Foobar::factory(data); 
+3

Tôi không có ý định đề cập đến 'phát hành()' để gây hiểu lầm hoặc gây nhầm lẫn, xin lỗi. Mã mới này đang đi vào một ứng dụng hiện có khá lớn (1-2 triệu dòng C++) không sử dụng bất kỳ con trỏ thông minh nào (ngoài COM).Tôi biết những người khác trong nhóm của tôi sẽ quan tâm nếu không có cách nào hợp lý để đến được con trỏ thô nếu mã kế thừa làm cho nó có hiệu quả "bắt buộc". Dĩ nhiên, tôi sẽ đưa ra các cảnh báo thích hợp và khuyên bạn nên gắn bó với unique_ptr/shared_ptr khi có thể. Tôi thực sự thích ý tưởng typedef của bạn, trong đó đặt câu trả lời của bạn ngoài James McNellis '. Cảm ơn vì những hiểu biết. –

17

A std::unique_ptr duy nhất sở hữu đối tượng mà nó trỏ. Nó nói "Tôi sở hữu đối tượng này, và không ai khác làm." Đó là chính xác những gì bạn đang cố gắng thể hiện: bạn đang nói "người gọi chức năng này: bạn bây giờ là chủ sở hữu duy nhất của đối tượng này; làm với nó như bạn vui lòng, tuổi thọ của nó là trách nhiệm của bạn."

+0

Thật vậy, nhưng tại sao không có nó trở lại một 'std :: shared_pointer <>'? Nó đạt được hiệu quả tương tự nhưng cho phép nhiều bản sao của con trỏ. –

+8

@ AndréCaron: Tại sao áp dụng việc sử dụng 'std :: shared_ptr' nếu tất cả những gì bạn muốn làm là chuyển quyền sở hữu cho người gọi? Hãy để người gọi cho quyền sở hữu đối tượng với 'std :: shared_ptr' nếu nó muốn (hoặc một kiểu con trỏ thông minh khác, nếu người gọi muốn). –

+7

@ André Caron: Lý do cơ bản là hiệu suất. Nhà máy không thể biết liệu chức năng của máy khách có được bổ sung bởi 'std :: shared_ptr' trên' std :: unique_ptr' hay không, vậy tại sao lại phải hi sinh hiệu năng cho một số chức năng, điều này có thể không cần thiết. Vì 'shared_ptr' có thể được xây dựng và được gán cho từ' unique_ptr', nên thực sự không có lợi ích nào – Grizzly

6

Nó chính xác chuyển tải ngữ nghĩa chính xác và là cách tôi nghĩ rằng tất cả các nhà máy trong C++ nên hoạt động: std::unique_ptr<T> không áp đặt bất kỳ loại ngữ nghĩa quyền sở hữu nào và nó cực kỳ rẻ.

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