2013-06-15 27 views
26

Tôi là một nhà phát triển C dày dạn, hiện đang tham gia vào C++ và tôi phải thừa nhận, tôi rất bối rối về việc có bao nhiêu cách để tạo, lưu giữ và hủy các đối tượng C++. Trong C, cuộc sống rất đơn giản: phân công với = bản sao trên ngăn xếp và malloc/free quản lý dữ liệu trên heap. C + + là xa đó, hoặc vì vậy nó có vẻ với tôi.Vòng đời của đối tượng C++ là gì?

Trong ánh sáng đó, đây là những câu hỏi của tôi:

  1. tất cả những cách để tạo ra một đối tượng C++? Trực tiếp/sao chép constructor, phân công, vv Làm thế nào để họ làm việc?
  2. Tất cả các cú pháp khởi tạo khác nhau được liên kết với tất cả các loại tạo đối tượng này là gì? Sự khác nhau giữa T f = x, T f(x);, T f{x};, v.v. là gì?
  3. Quan trọng nhất, khi là chính xác để sao chép/gán/bất cứ điều gì = có trong C++ và khi nào bạn muốn sử dụng con trỏ? Trong C, tôi đã rất quen với việc ném các con trỏ quanh một số , bởi vì gán con trỏ là rẻ nhưng việc sao chép cấu trúc lại ít hơn. Làm thế nào để ngữ nghĩa sao chép của C++ ảnh hưởng đến điều này?
  4. Cuối cùng, tất cả những điều này như thế nào là shared_ptr, weak_ptr, v.v ...?

Tôi xin lỗi nếu đây là một câu hỏi khá rộng, nhưng tôi rất bối rối khi sử dụng cái gì (thậm chí không nhắc đến sự nhầm lẫn của tôi về quản lý bộ nhớ trong bộ sưu tập và toán tử new). mọi thứ tôi biết về quản lý bộ nhớ C bị hỏng trong C++. Đó có phải là sự thật, hay là mô hình tâm thần của tôi sai?

Để tổng hợp mọi thứ: các đối tượng C++ được tạo, khởi tạo và hủy như thế nào và khi nào tôi nên sử dụng từng phương pháp?

+0

bạn có quan tâm đến C++ 03 hoặc trong C++ 11 không? – Alex

+4

Bạn có lẽ nên chia nhỏ thành các bài đăng nhỏ hơn, một câu hỏi duy nhất. – juanchopanza

+0

Tôi nhớ đọc về chúng trong nguyên tắc của tôi về ngôn ngữ lập trình khóa học – pinkpanther

Trả lời

21

Trước hết, kỹ năng quản lý bộ nhớ của bạn rất hữu ích trong C++, chỉ cần họ là một mức độ thấp hơn C++ cách làm việc, nhưng họ đang có ...

Về câu hỏi của bạn, họ là một chút rộng , vì vậy tôi sẽ cố gắng giữ nó ngắn gọn:

1) Tất cả các cách để tạo đối tượng C++ là gì?

Tương tự như C: chúng có thể là biến toàn cục, tự động địa phương, tĩnh cục bộ hoặc động. Bạn có thể bị nhầm lẫn bởi hàm tạo, nhưng chỉ đơn giản nghĩ rằng mỗi khi bạn tạo một đối tượng, một hàm tạo được gọi. Luôn luôn. Phương thức khởi tạo nào đơn giản là vấn đề của tham số nào được sử dụng khi tạo đối tượng.

Chuyển nhượng không tạo đối tượng mới, nó chỉ đơn giản là sao chép từ một trong những khác nhau, (nghĩ về memcpy nhưng thông minh hơn).

2) Tất cả các cú pháp khởi tạo khác nhau được liên kết với tất cả các loại tạo đối tượng này là gì? Sự khác biệt giữa T f = x, T f (x) ;, T f {x} ;, v.v.

  • T f(x) là cách cổ điển, nó chỉ đơn giản tạo ra một đối tượng kiểu T sử dụng constructor mà mất x như là đối số.
  • T f{x} là cú pháp hợp nhất C++ 11 mới, vì nó có thể được sử dụng để khởi tạo các loại tổng hợp (mảng và như vậy), nhưng khác với nó tương đương với trước đây.
  • T f = x tùy thuộc vào việc x có thuộc loại T hay không. Nếu có, thì nó tương đương với cái cũ, nhưng nếu nó thuộc loại khác, thì nó tương đương với T f = T(x). Không phải là nó thực sự quan trọng, bởi vì trình biên dịch được cho phép để tối ưu hóa các bản sao thêm (sao chép elision).
  • T(x). Bạn quên cái này. Một đối tượng tạm thời kiểu T được tạo (sử dụng cùng một hàm tạo như trên), nó được sử dụng bất cứ nơi nào xảy ra trong mã và ở cuối biểu thức đầy đủ hiện tại, nó bị hủy.
  • T f.Điều này tạo ra một giá trị loại T bằng cách sử dụng hàm tạo mặc định, nếu có. Đó chỉ đơn giản là một hàm tạo không có tham số.
  • T f{}. Mặc định được contructed, nhưng với cú pháp hợp nhất mới. Lưu ý rằng T f() không phải là đối tượng thuộc loại T, nhưng thay vào đó, một hàm trả về T!.
  • T(). Một đối tượng tạm thời sử dụng hàm tạo mặc định.

3) Quan trọng nhất là khi sao chép/gán/bất kỳ = bằng C++ và khi nào bạn muốn sử dụng con trỏ?

Bạn có thể sử dụng giống như trong C. Hãy nghĩ về bản sao/bài tập như thể nó ở đâu memcpy. Bạn cũng có thể chuyển tài liệu tham khảo xung quanh, nhưng bạn cũng có thể đợi một lúc cho đến khi bạn cảm thấy thoải mái với những tài liệu đó. Những gì bạn nên làm là: không sử dụng con trỏ làm biến cục bộ phụ trợ, sử dụng tham chiếu thay thế.

4) Cuối cùng, tất cả những thứ này như shared_ptr, weak_ptr, v.v ... là gì?

Chúng là các công cụ trong vành đai công cụ C++ của bạn. Bạn sẽ phải học qua trải nghiệm và một số sai lầm ...

  • shared_ptr sử dụng khi quyền sở hữu của đối tượng được chia sẻ.
  • unique_ptr sử dụng khi quyền sở hữu đối tượng là duy nhất và không rõ ràng.
  • weak_ptr được sử dụng để ngắt vòng lặp trên cây của shared_ptr. Chúng là không phải được phát hiện tự động.
  • vector. Đừng quên cái này! Sử dụng nó để tạo mảng động của bất cứ thứ gì.

PS: Bạn quên hỏi về destructor. IMO, destructors là những gì mang lại cho C++ tính cách của nó, vì vậy hãy chắc chắn để sử dụng rất nhiều trong số họ!

+2

Bạn đã quên 'T t;' và 'T t {}' trong 2). – juanchopanza

+0

@juanchopanza: Ops! Thêm chúng. – rodrigo

+0

Tuyệt vời. Cảm ơn bạn đã trả lời đầy đủ. Tôi biết rất nhiều thông tin này nằm rải rác xung quanh SO, nhưng đây là lần đầu tiên tôi nghĩ rằng tôi đã nhìn thấy một câu trả lời mà thực sự liệt kê sự khác biệt giữa * tất cả * các loại cú pháp khởi tạo rõ ràng. –

4

Đây là một câu hỏi khá rộng, nhưng tôi sẽ cung cấp cho bạn một điểm khởi đầu.

Điểm C được gọi là "biến ngăn xếp" cũng được gọi là đối tượng có "bộ nhớ tự động". Thời gian tồn tại của một đối tượng với lưu trữ tự động là khá dễ hiểu: nó được tạo ra khi kiểm soát đạt điểm nó được xác định, và sau đó bị phá hủy khi nó đi ra khỏi phạm vi:

int main() { 
    int foo = 5; // creation of automatic storage 
    do_stuff(); 
    foo = 1; 

    // end of function; foo is destroyed. 
} 

Bây giờ, một điều cần lưu ý là = 5 được coi là một phần của cú pháp khởi tạo, trong khi = 1 được coi là một thao tác gán. Tôi không muốn bạn bị nhầm lẫn bởi = đang được sử dụng cho hai thứ khác nhau trong ngữ pháp của ngôn ngữ.

Dù sao, C++ tự động lưu trữ thêm một chút và cho phép mã tùy ý chạy trong khi tạo và hủy đối tượng đó: các hàm tạo và hàm hủy. Điều này dẫn đến thành ngữ tuyệt vời được gọi là RAII, mà bạn nên sử dụng bất cứ khi nào có thể. Với RAII, việc quản lý tài nguyên trở nên tự động.

tất cả những thứ này như shared_ptr, weak_ptr, v.v ...?

Ví dụ hay về RAII. Chúng cho phép bạn xử lý một tài nguyên động (cuộc gọi malloc/miễn phí) như một đối tượng lưu trữ tự động!

Quan trọng nhất là khi sao chép/gán/bất cứ điều gì = trong C++ và khi nào bạn muốn sử dụng con trỏ? Trong C, tôi đã rất quen với việc ném con trỏ xung quanh rất nhiều, bởi vì gán con trỏ là rẻ nhưng cấu trúc sao chép là ít hơn như vậy. Làm thế nào để ngữ nghĩa sao chép của C++ ảnh hưởng đến điều này?

const tham chiếu ở mọi nơi, đặc biệt là tham số chức năng. const tránh sao chép và ngăn không cho sửa đổi đối tượng. Nếu bạn không thể sử dụng const ref, rất có thể là một tham chiếu bình thường là phù hợp. Nếu vì lý do nào đó bạn muốn đặt lại tham chiếu hoặc đặt nó thành null, hãy sử dụng một con trỏ.

Tất cả các cách để tạo đối tượng C++ là gì? Trực tiếp/sao chép constructor, phân công, vv Làm thế nào để họ làm việc?

Trong ngắn hạn, tất cả các nhà thầu tạo đối tượng. Bài tập không. Đọc một cuốn sách cho việc này.

+0

Điều này thực sự là siêu hữu ích và làm rõ và có lẽ đủ để chỉ cho tôi đi đúng hướng. Cảm ơn rất nhiều! Thật không may, tôi đã lên kế hoạch để nuke câu hỏi này và thử cái gì khác, nhưng bây giờ tôi không nghĩ rằng tôi có thể. Tôi đoán tôi sẽ để nó dính quanh, vì nó có thể hữu ích cho người khác. –

2
  1. Có nhiều cách tạo đối tượng tiềm ẩn trong C++ ngoài các đối tượng rõ ràng. Hầu như tất cả đều sử dụng copy-constructor của class của đối tượng. Hãy nhớ rằng: Sao chép ngầm định có thể yêu cầu người tạo bản sao và/hoặc toán tử gán của loại T được khai báo trong phạm vi public tùy thuộc vào việc sao chép xảy ra.
    Vì vậy, trong khóa học:

    a) tạo ra rõ ràng của một thương hiệu đối tượng mới trong stack:

    T object(arg);

b) sao chép rõ ràng của một đối tượng hiện có:

T original(arg); 
... 
T copy(original); 

Nếu T lớp không có hàm tạo bản sao được xác định mặc định thực hiện được tạo bởi trình biên dịch. Nó cố gắng tạo một bản sao chính xác của đối tượng được truyền. Đây không phải lúc nào cũng là lập trình viên muốn, do đó, việc triển khai tùy chỉnh đôi khi có thể hữu ích.
c) tạo rõ ràng của một đối tượng thương hiệu mới trong đống:

T *ptr = new T(arg); 

d) tạo tiềm ẩn của một thương hiệu đối tượng mới mà constructor chỉ mất một tham số và không có sửa đổi explicit, ví dụ:

class T 
{ 
public: 
    T(int x) : i(x) {} 
private: 
    int i; 
} 
... 
T object = 5; // actually implicit invocation of constructor occurs here 

e) sao chép ngầm của một đối tượng thông qua với một hàm theo giá trị:

void func(T input) 
{ 
    // here `input` is a copy of an object actually passed 
} 
... 

int main() 
{ 
    T object(arg); 
    func(object); // copy constructor of T class is invoked before the `func` is called 
} 

f) im sao chép sơ bộ đối tượng xử lý ngoại lệ theo giá trị:

void function() 
{ 
    ... 
    throw T(arg); // suppose that exception is always raised in the `function` 
    ... 
} 
... 
int main() 
{ 
    ... 
    try { 
     function(); 
    } catch (T exception) { // copy constructor of T class is invoked here 
     // handling `exception` 
    } 
    ... 
} 

g) Tạo đối tượng mới bằng toán tử gán. Tôi đã không sử dụng từ 'sao chép' bởi vì trong trường hợp này, việc thực thi toán tử gán của một kiểu cụ thể quan trọng. Nếu toán tử này không được triển khai thực hiện mặc định được tạo bởi trình biên dịch, btw nó có hành vi giống như hàm tạo bản sao mặc định.

class T 
{ 
    T(int x) : i(x) {} 
    T operator=() const 
    { 
     return T(*this); // in this implementation we explicitly call default copy constructor 
    } 
} 
... 
int main() 
{ 
    ... 
    T first(5); 
    T second = first; // assingment operator is invoked 
    ... 
} 

Vâng, đó là những gì tôi có thể nhớ mà không cần nhìn vào cuốn sách của Stroustrup. Có thể là một cái gì đó bị mất.
Trong khi tôi đang viết điều này, một số câu trả lời đã được chấp nhận nên tôi dừng lại ở thời điểm này. Có thể chi tiết tôi liệt kê sẽ hữu ích.

+0

Bạn đã bỏ lỡ _object được trả về từ trường hợp function_. Điểm _g) _ của bạn sai: toán tử gán không tạo đối tượng mới mà thay đổi đối tượng hiện tại. Thực tế là ví dụ của bạn trả về 'T' và vì vậy nó thực sự tạo ra một đối tượng mới chỉ là một tai nạn. – rodrigo

+0

@rodrigo, bạn nói đúng.Tôi đã từng quá tải các nhà khai thác khá lâu rồi, vì vậy tôi đã phải sửa lại điểm g) trước khi viết – Ivan

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