2010-03-24 25 views
18

Tôi có các đối tượng tạo các đối tượng con khác trong các hàm tạo của chúng, chuyển 'this this' để con có thể lưu một con trỏ trở lại con trỏ của nó. Tôi sử dụng boost :: shared_ptr rộng rãi trong chương trình của tôi như là một thay thế an toàn hơn cho std :: auto_ptr hoặc raw pointers. Vì vậy, đứa trẻ sẽ có mã như shared_ptr<Parent>, và tăng cung cấp các phương pháp shared_from_this() mà cha mẹ có thể cung cấp cho đứa trẻ.Làm thế nào để xử lý con trỏ 'này' trong hàm tạo?

Vấn đề của tôi là shared_from_this() không thể được sử dụng trong một hàm tạo, điều này không thực sự là một tội phạm bởi vì 'điều này' không nên được sử dụng trong một nhà xây dựng, trừ khi bạn biết bạn đang làm gì và không quan tâm đến những hạn chế .

Hướng dẫn kiểu C++ của Google states rằng các nhà xây dựng chỉ nên đặt biến thành viên thành giá trị ban đầu của chúng. Bất kỳ khởi tạo phức tạp nào cũng nên đi theo phương thức Init() rõ ràng. Điều này giải quyết vấn đề 'này-trong-constructor' cũng như một vài người khác là tốt.

Điều làm phiền tôi là những người sử dụng mã của bạn bây giờ phải nhớ gọi Init() mỗi khi họ xây dựng một trong các đối tượng của bạn. Cách duy nhất tôi có thể nghĩ đến để thực thi điều này là bằng cách xác nhận rằng Init() đã được gọi ở đầu mỗi hàm thành viên, nhưng điều này là tẻ nhạt để viết và rườm rà để thực thi.

Có thành ngữ nào ở đó giải quyết vấn đề này ở bất kỳ bước nào trên đường đi không?

+17

Google đã sai. Có một chủ đề khổng lồ về điều này và các bài viết ngắn khác về hướng dẫn về phong cách của họ trên comp.lang.C++. Lý do cơ bản cho họ đến với điều này là bởi vì họ cũng (sai) cấm ngoại lệ. –

+3

@Neil: Ấn tượng mà tôi nhận được là Google đã được trao tay nặng với hướng dẫn phong cách để chứa những người không biết rõ C++, cố gắng hạn chế thiệt hại mà họ có thể làm hoặc có thể thực thi tính đồng nhất với mã cũ. Google có thể có lý do chính đáng cho hướng dẫn về phong cách của họ, nhưng nó không phải là một mô hình tốt cho bất kỳ ai khác để sao chép. –

+9

@David Đúng vậy. Nhưng mọi người có thể bị nhầm lẫn khi sử dụng các số dấu phẩy động (xem tất cả các câu hỏi ở đây về chủ đề) hoặc thậm chí là số nguyên, nhưng chúng tôi không cấm sử dụng chúng. Đây là một trong những lý do tôi không có fan hâm mộ của phong cách hướng dẫn. –

Trả lời

16

Sử dụng phương pháp nhà máy để xây dựng 2 pha & khởi tạo lớp học của bạn, sau đó đặt hàm ctor & Init() riêng tư. Sau đó, không có cách nào để tạo đối tượng của bạn không chính xác. Chỉ cần nhớ để giữ cho các công destructor và sử dụng một con trỏ thông minh:

#include <memory> 

class BigObject 
{ 
public: 
    static std::tr1::shared_ptr<BigObject> Create(int someParam) 
    { 
     std::tr1::shared_ptr<BigObject> ret(new BigObject(someParam)); 
     ret->Init(); 
     return ret; 
    } 

private: 
    bool Init() 
    { 
     // do something to init 
     return true; 
    } 

    BigObject(int para) 
    { 
    } 

    BigObject() {} 

}; 


int main() 
{ 
    std::tr1::shared_ptr<BigObject> obj = BigObject::Create(42); 
    return 0; 
} 

EDIT:

Nếu bạn muốn phản đối việc sống trên stack, bạn có thể sử dụng một biến thể của mô hình trên. Theo văn bản này sẽ tạo ra một tạm thời và sử dụng bản sao ctor:

#include <memory> 

class StackObject 
{ 
public: 
    StackObject(const StackObject& rhs) 
     : n_(rhs.n_) 
    { 
    } 

    static StackObject Create(int val) 
    { 
     StackObject ret(val); 
     ret.Init(); 
     return ret; 
    } 
private: 
    int n_; 
    StackObject(int n = 0) : n_(n) {}; 
    bool Init() { return true; } 
}; 

int main() 
{ 
    StackObject sObj = StackObject::Create(42); 
    return 0; 
} 
+0

Nhân tiện, tôi không chắc chắn rằng thuật ngữ chính xác cho thiết bị này là "phương pháp nhà máy", nhưng đó là những gì tôi đã luôn gọi nó. –

+2

Thuật ngữ này là chính xác và tôi biết không tốt hơn, nhưng tôi muốn trả lại một con trỏ thông minh từ Tạo. –

+1

Nhược điểm của khóa học là bạn không thể đặt đối tượng trên ngăn xếp. –

8

Nguyên tắc lập trình C++ của Google đã bị chỉ trích ở đây và ở nơi khác một lần nữa và một lần nữa. Và đúng như vậy.

Tôi chỉ sử dụng khởi tạo hai giai đoạn nếu nó bị ẩn sau lớp gói. Nếu các hàm khởi tạo gọi thủ công sẽ hoạt động, chúng ta vẫn sẽ lập trình trong C và C++ với các hàm tạo của nó sẽ không bao giờ được phát minh.

+3

+1. Điều quan trọng cần nhớ là các nguyên tắc của Google được viết chủ yếu cho việc sử dụng * của họ chứ không phải cho toàn bộ cộng đồng C++ và xem xét tất cả các mã và thực hành hiện tại của họ, thay vì phục vụ như hướng dẫn cách viết các dự án chung mới. –

0

Một đối tượng đòi hỏi phải xây dựng phức tạp âm thanh như một công việc cho một nhà máy.

Xác định giao diện hoặc lớp trừu tượng, không thể xây dựng, cộng với chức năng miễn phí, có thể có tham số, trả về con trỏ tới giao diện nhưng phía sau cảnh sẽ phức tạp.

Bạn phải nghĩ về thiết kế theo những gì người dùng cuối của lớp học của bạn phải làm.

5

Tùy thuộc vào tình huống, đây có thể là trường hợp con trỏ được chia sẻ không thêm bất kỳ thứ gì. Họ nên được sử dụng bất cứ lúc nào quản lý suốt đời là một vấn đề. Nếu đối tượng con thọ suốt được đảm bảo ngắn hơn giá trị của cha mẹ, tôi không thấy vấn đề với việc sử dụng con trỏ thô. Ví dụ, nếu cha mẹ tạo và xóa các đối tượng con (và không có ai khác làm), không có câu hỏi nào về việc ai sẽ xóa các đối tượng con.

+4

Và đặt con trỏ gốc vào con trỏ thông minh rời khỏi trẻ em trong cuộc sống của cha mẹ, mà chỉ là một lỗi chờ đợi để xảy ra. Xóa đứa trẻ cuối cùng, và * poof! * Bạn lấy tấm thảm kéo ra từ dưới bạn. –

3

KeithB có một điểm thực sự tốt mà tôi muốn mở rộng (theo nghĩa rằng không liên quan đến câu hỏi, nhưng điều đó sẽ không phù hợp trong một chú thích):

Trong trường hợp cụ thể của mối quan hệ của một đối tượng với các subobject của nó, các vòng đời được đảm bảo: đối tượng cha sẽ luôn luôn sống lâu hơn đối tượng con. Trong trường hợp này đối tượng con (thành viên) không chia sẻ quyền sở hữu đối tượng gốc (có chứa) và không được sử dụng shared_ptr. Nó không nên được sử dụng vì lý do ngữ nghĩa (không có quyền sở hữu chung) và cũng không vì lý do thực tế: bạn có thể giới thiệu tất cả các loại vấn đề: rò rỉ bộ nhớ và xóa không chính xác.

Để giảm bớt thảo luận, tôi sẽ sử dụng P để chỉ đối tượng cha và C để chỉ đối tượng con hoặc đối tượng chứa.

Nếu P đời được bên ngoài xử lý với một shared_ptr, sau đó thêm shared_ptr khác trong C để chỉ P sẽ có tác dụng tạo ra một chu kỳ. Khi bạn có một chu kỳ trong bộ nhớ được quản lý bằng cách tính tham chiếu, hầu hết có thể có rò rỉ bộ nhớ: khi bên ngoài cuối cùng shared_ptr đề cập đến P nằm ngoài phạm vi, con trỏ trong C vẫn còn hoạt động, vì vậy số tham chiếu cho P không đạt được 0 và đối tượng không được phát hành, ngay cả khi đối tượng không còn truy cập được nữa.

Nếu P được xử lý bằng một con trỏ khác thì khi con trỏ bị xóa, nó sẽ gọi hàm hủy là P, sẽ xếp vào số điện thoại gọi là số C. Số tham chiếu cho P trong số shared_ptr rằng C sẽ đạt đến 0 và nó sẽ kích hoạt thao tác xóa kép.

Nếu P có thời lượng lưu trữ tự động, khi đối tượng không được gọi (đối tượng nằm ngoài phạm vi hoặc hàm hủy đối tượng chứa) thì shared_ptr sẽ kích hoạt việc xóa khối bộ nhớ không phải là mới.

Giải pháp phổ biến là phá vỡ chu kỳ với weak_ptr s, để đối tượng con sẽ không giữ shared_ptr cho cha mẹ, mà là weak_ptr. Ở giai đoạn này vấn đề là như nhau: để tạo ra một weak_ptr đối tượng phải được quản lý bởi một shared_ptr, trong khi xây dựng không thể xảy ra.

Cân nhắc sử dụng con trỏ thô (xử lý quyền sở hữu tài nguyên thông qua con trỏ không an toàn, nhưng quyền sở hữu ở đây được xử lý bên ngoài để không phải là vấn đề) hoặc thậm chí là tham chiếu (cũng là nói với các lập trình viên khác mà bạn tin tưởng đối tượng được giới thiệu P để đưa ra đối tượng giới thiệu C)

+0

Tôi không thích ý tưởng ghép nối các lớp với một con trỏ thông minh cụ thể. Với C++ 0x, một nửa sử dụng 'shared_ptr' có thể được thay thế bằng' unique_ptr' (tôi không bao giờ muốn chia sẻ bất cứ thứ gì, tôi chỉ muốn đặt con trỏ vào một vùng chứa). – UncleBens

+0

Tôi nghĩ rằng tôi là người duy nhất * ích kỷ * xung quanh không muốn * chia sẻ * mọi thứ. Từ một quan điểm ngữ nghĩa, tôi cũng giống như các con trỏ có phạm vi, thậm chí nếu chúng không làm cho nó trở thành tiêu chuẩn ... –

+0

Điều tôi thích về 'shared_ptr' là thuộc tính cách ly mã chúng có: bạn có thể thao tác' shared_ptr 'với một tuyên bố đơn giản 'class Foo;' forward và chưa giải phóng bộ nhớ một cách chính xác. Không ai trong số các con trỏ khác có nó (có, nó đòi hỏi một số công việc). –

0

Bạn có thực sự cần sử dụng shared_ptr trong trường hợp này không? Đứa trẻ có thể có con trỏ không? Sau khi tất cả, đó là đối tượng con, do đó, nó thuộc sở hữu của cha mẹ, vì vậy nó không thể chỉ có một con trỏ bình thường để nó là cha mẹ?

+0

Tôi cho rằng nó sẽ phụ thuộc nếu 'Child' có thể được chuyển cho một thực thể khác (bản sao). –

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