2009-05-11 51 views
19

Tôi khá mới đối với C++ và tôi không chắc chắn về điều này. Hãy xem ví dụ sau đây tóm tắt vấn đề hiện tại của tôi.C++ - xây dựng một đối tượng bên trong một lớp

class Foo 
{ 
    //stuff 
}; 

class Bar 
{ 
    Foo foo; 
}; 

Vì vậy, thanh sẽ chứa đối tượng Foo đầy đủ, không chỉ là tham chiếu hoặc con trỏ. Đối tượng này có được khởi tạo bởi hàm tạo mặc định của nó không? Tôi có cần gọi một cách rõ ràng hàm khởi tạo của nó không, và nếu có, làm thế nào và ở đâu?

Cảm ơn.

+4

Tôi quen thuộc với C++ và tôi vẫn có một số nghi ngờ về mức độ này thường xuyên. Hơn nữa, hầu hết các câu trả lời đều nói rõ rằng hàm tạo mặc định Foo sẽ được gọi và thực tế là nó phụ thuộc vào định nghĩa của chính Foo. Nó là một người sử dụng cung cấp hoặc ngầm định constructor? Nó có thuộc tính thành viên riêng nào không? Khởi tạo trong C++ không đơn giản. –

+2

Khá buồn cười rằng @xtofl yêu cầu người đăng ký xóa 'Tôi quen thuộc với C++' ... Có lẽ hầu hết mọi người không phải là 'quen thuộc' với C++ khi gần như tất cả các câu trả lời đều sai. Trong thực tế khởi tạo là khó, một số người trả lời đã chứng minh kiến ​​thức C++ của họ @JaredPar, @dirkgently, @David Thornley và thất bại. –

Trả lời

17

Nó sẽ được khởi tạo bởi nó nhà xây dựng mặc định. Nếu bạn muốn sử dụng một hàm tạo khác, bạn có thể có một cái gì đó như thế này:

class Foo 
{ 
    public: 
    Foo(int val) { } 
    //stuff 
}; 

class Bar 
{ 
    public: 
    Bar() : foo(2) { } 

    Foo foo; 
}; 
+0

Lỗi cú pháp: bạn cần dấu hai chấm (:) sau từ khóa công khai. – dirkgently

+2

Và dấu chấm phẩy (;) sau khi đóng} của mỗi lớp. Tôi đổ lỗi cho Java. – richq

+0

cảm ơn các sửa đổi cú pháp. Tôi đổ lỗi cho C#. –

1

So Bar constains a full Foo object, not just a reference or pointer. Is this object initialized by its default constructor?

Nếu Foo có một ctor mặc định, một đối tượng kiểu Foo sẽ sử dụng ctor mặc định khi bạn tạo một đối tượng kiểu Bar. Nếu không, bạn cần phải tự gọi cho chính mình hoặc số Bar của ctor sẽ làm cho trình biên dịch của bạn phàn nàn loduly.

Ví dụ:

class Foo { 
public: 
Foo(double x) {} 
}; 

class Bar { 
Foo x; 
}; 

int main() { 
Bar b; 
} 

trên sẽ có trình biên dịch phàn nàn cái gì đó như:

"In constructor 'Bar::Bar()': Line 5: error: no matching function for call to 'Foo::Foo()'

Do I need to explicitly call its constructor, and if so, how and where ?

Sửa đổi các ví dụ trên như sau:

class Foo { 
public: 
    Foo(double x) {} // non-trivial ctor 
}; 

class Bar {  
Foo x; 
public: 
    Bar() : x(42.0) {} // non-default ctor, so public access specifier required 
}; 

int main() { 
Bar b; 
} 
+0

Nếu Foo không có bất kỳ hàm tạo nào được định nghĩa (bên cạnh có thể là trình tạo bản sao) thì trình biên dịch sẽ không phàn nàn chút nào nhưng sẽ không gọi hàm tạo Foo ngầm được xác định rõ ràng. Đối tượng sẽ không được khởi tạo. –

+0

Đó là những gì tôi có nghĩa là bởi ctor mặc định. – dirkgently

+0

Có thể khởi tạo đối tượng mà không sử dụng danh sách khởi tạo không? Như trong, bạn có thể khởi tạo đối tượng bên trong {} của hàm tạo không? –

2

Nếu bạn không gọi rõ ràng một hàm tạo của foo bên trong hàm tạo của Bar thì giá trị mặc định sẽ được sử dụng. Bạn có thể kiểm soát này bằng cách gọi một cách rõ ràng các nhà xây dựng

Bar::Bar() : foo(42) {} 

Đây là khóa học giả sử bạn thêm một Foo :: Foo (int) vào mã :)

+1

Tôi thấy rằng hầu hết các bạn đã nghĩ ra "42" trong ví dụ của mình. cái đó từ đâu tới? – ichiban

+7

Đó là một tham chiếu đến H2G2 - câu trả lời cho câu hỏi cuối cùng của cuộc sống, vũ trụ và mọi thứ. Đọc: http://en.wikipedia.org/wiki/42_(number)#In_The_Hitchhiker.27s_Guide_to_the_Galaxy. Thậm chí tốt hơn đọc Douglas Adams. – dirkgently

+0

@ichiban, @dirkgently là chính xác. – JaredPar

1

Full đối tượng. Không, nó được xây dựng mặc định trong constructor mặc định của Bar.

Bây giờ, nếu Foo có một hàm tạo chỉ lấy, ví dụ: int. Bạn sẽ cần một constructor trong Bar để gọi constructor Foo, và nói gì đó là:

class Foo { 
public: 
    Foo(int x) { .... } 
}; 

class Bar { 
public: 
    Bar() : foo(42) {} 

    Foo foo; 
}; 

Nhưng nếu Foo có một constructor mặc định Foo(), trình biên dịch tạo ra constructor Bar tự động, và đó sẽ gọi mặc định của Foo (ví dụ: Foo())

+0

Tôi thấy rằng hầu hết các bạn đã đưa ra "42" trong ví dụ của mình. cái đó từ đâu tới? – ichiban

+0

Không hoạt động, tất cả các ctors đều được đặt ở chế độ riêng tư theo mặc định. – dirkgently

+0

Rất tiếc. Đã sửa. Tôi có xu hướng bỏ điều đó ra khi suy nghĩ/nói về các khái niệm khác. – Macke

1

Trừ khi bạn chỉ định khác, foo được khởi tạo bằng cách sử dụng hàm tạo mặc định của nó. Nếu bạn muốn sử dụng một số nhà xây dựng khác, bạn cần phải làm như vậy trong danh sách khởi tạo cho Bar:

Bar::Bar(int baz) : foo(baz) 
{ 
    // Rest of the code for Bar::Bar(int) goes here... 
} 
+0

Nếu Foo có một hàm tạo mặc định do người dùng định nghĩa thì nó sẽ được gọi. Đối với một hàm tạo mặc định được xác định ngầm định trong Foo, sẽ không có cuộc gọi nào được thực hiện trong hàm tạo ngầm được định nghĩa của Bar. –

+0

Có thể khởi tạo đối tượng mà không sử dụng danh sách khởi tạo không? Như trong, bạn có thể khởi tạo đối tượng bên trong {} của hàm tạo không? –

+0

@JustinLiang, không, khi cơ thể của hàm tạo được thực hiện bất kỳ đối tượng nào là một phần của lớp sẽ được khởi tạo đã được khởi tạo. Nếu đối tượng không có một hàm tạo mặc định, hãy gọi hàm tạo trong danh sách khởi tạo là bắt buộc. Tuy nhiên, bạn có thể sử dụng nhiệm vụ để thay đổi đối tượng và dữ liệu trong lớp, nhưng được cảnh báo rằng điều này kém hiệu quả hơn vì đối tượng đang được khởi tạo và sau đó ghi đè bằng gán. – Naaff

0

Bạn không cần phải gọi các contructor mặc định một cách rõ ràng trong C++, nó sẽ được gọi cho bạn. Nếu bạn muốn gọi một contructor khác nhau, bạn có thể làm điều này:

Foo foo(somearg) 
+0

Nếu Foo có một hàm tạo mặc định do người dùng định nghĩa thì nó sẽ được gọi. Đối với một hàm tạo mặc định ngầm định trong Foo, không có cuộc gọi nào được thực hiện trong hàm tạo ngầm được định nghĩa của Bar –

4

Có bốn chức năng C++ biên dịch sẽ tạo ra cho mỗi lớp học, nếu nó có thể, và nếu bạn không cung cấp cho họ: một constructor mặc định , một hàm tạo bản sao, toán tử gán và hàm hủy.Trong tiêu chuẩn C++ (chương 12, "Các hàm đặc biệt"), chúng được gọi là "được khai báo ngầm" và "được định nghĩa ngầm". Họ sẽ có quyền truy cập công cộng.

Đừng nhầm lẫn "được xác định ngầm" với "mặc định" trong hàm tạo. Hàm khởi tạo mặc định là một hàm có thể được gọi mà không có bất kỳ đối số nào, nếu có. Nếu bạn không cung cấp hàm tạo nào, một giá trị mặc định sẽ được định nghĩa ngầm định. Nó sẽ sử dụng các hàm tạo mặc định cho mỗi lớp cơ sở và thành viên dữ liệu. Vì vậy, những gì đang xảy ra là lớp Foo có một hàm khởi tạo mặc định được định nghĩa ngầm định, và Bar (mà dường như không có một hàm tạo do người dùng định nghĩa) sử dụng constructor mặc định được định nghĩa ngầm của nó gọi hàm dựng mặc định của Foo.

Nếu bạn muốn viết một hàm tạo cho Bar, bạn có thể đề cập đến foo trong danh sách khởi tạo của nó, nhưng vì bạn đang sử dụng hàm tạo mặc định, bạn không thực sự phải chỉ định nó. Hãy nhớ rằng, nếu bạn viết một hàm tạo cho Foo, trình biên dịch sẽ không tự động tạo ra một hàm khởi tạo mặc định, và vì vậy bạn sẽ phải chỉ định một hàm khởi tạo nếu bạn cần một trình biên dịch. Ví dụ:. Do đó, nếu bạn đặt một số thứ như Foo(int n); vào định nghĩa của Foo và không viết rõ ràng một hàm tạo mặc định (hoặc Foo(); hoặc Foo(int n = 0);), bạn không thể có Bar ở dạng hiện tại vì nó không thể sử dụng Nhà xây dựng mặc định của Foo. Trong trường hợp này, bạn sẽ phải có một hàm tạo như Bar(int n = 0): foo(n) {} khi hàm tạo Bar khởi tạo Foo. (Lưu ý rằng Bar(int n = 0) {foo = n;} hoặc những thứ tương tự sẽ không hoạt động, vì hàm tạo Bar trước hết sẽ cố gắng khởi tạo foo và điều đó sẽ thất bại.)

+0

Hàm tạo ngầm được định nghĩa trong Bar sẽ KHÔNG gọi một hàm tạo ngầm được định nghĩa trong Foo. Nếu Foo có một constructor do người dùng định nghĩa thì Bar ngầm định nghĩa constructor sẽ (thời gian này có) gọi hàm tạo do người dùng định nghĩa trong Foo. Đây là câu trả lời hoàn chỉnh nhất cho đến đây: +1 –

+0

Chắc chắn về điều đó? Theo 12.6.2 (4), một thành viên dữ liệu nonstatic được khởi tạo với constructor mặc định của nó nếu không được đề cập trong danh sách initializer, và trong 8.5 (5) constructor mặc định được gọi trừ khi nó là POD (Plain Old Data, not convenient defined) trong Standard), trong trường hợp đó nó được khởi tạo bằng không. Vì vậy, tôi đoán câu hỏi là liệu Foo là một loại POD, mà chúng ta không thể nhìn thấy. Nếu đó là loại POD, tất cả đều được khởi tạo bằng 0; nếu nó có một trong những thứ đánh dấu nó là không phải POD, nó được khởi tạo mặc định và do đó gọi hàm khởi tạo mặc định ngầm định. –

12

Xây dựng là một chủ đề khá khó trong C++. Câu trả lời đơn giản là tùy thuộc vào. Cho dù Foo được khởi tạo hay không phụ thuộc vào định nghĩa của Foo. Về câu hỏi thứ hai: làm thế nào để làm cho Bar khởi tạo Foo: danh sách khởi tạo là câu trả lời.

Mặc dù sự đồng thuận chung là Foo sẽ được khởi tạo mặc định bởi hàm tạo mặc định ngầm (trình biên dịch được tạo), nhưng không cần phải giữ đúng.

Nếu Foo không có hàm tạo mặc định do người dùng xác định thì Foo sẽ không được khởi tạo. Để được chính xác hơn: mỗi thành viên của Bar hoặc Foo thiếu một người sử dụng định nghĩa constructor mặc định sẽ được uninitialized bởi trình biên dịch tạo ra constructor mặc định của Bar:

class Foo { 
    int x; 
public: 
    void dump() { std::cout << x << std::endl; } 
    void set() { x = 5; } 
}; 
class Bar { 
    Foo x; 
public: 
    void dump() { x.dump(); } 
    void set() { x.set(); } 
}; 
class Bar2 
{ 
    Foo x; 
public: 
    Bar2() : Foo() {} 
    void dump() { x.dump(); } 
    void set() { x.set(); } 
}; 
template <typename T> 
void test_internal() { 
    T x; 
    x.dump(); 
    x.set(); 
    x.dump(); 
} 
template <typename T> 
void test() { 
    test_internal<T>(); 
    test_internal<T>(); 
} 
int main() 
{ 
    test<Foo>(); // prints ??, 5, 5, 5, where ?? is a random number, possibly 0 
    test<Bar>(); // prints ??, 5, 5, 5 
    test<Bar2>(); // prints 0, 5, 0, 5 
} 

Bây giờ, nếu Foo có một người sử dụng định nghĩa constructor sau đó nó sẽ được khởi tạo luôn, bất kể Bar có hay không một người tạo khởi tạo. Nếu Bar có một hàm tạo do người dùng định nghĩa để gọi một cách rõ ràng hàm tạo (có thể được xác định ngầm định) của Foo, thì thực tế Foo sẽ được khởi tạo. Nếu danh sách khởi tạo của Bar không gọi hàm tạo Foo thì nó sẽ tương đương với trường hợp Bar không có hàm tạo do người dùng định nghĩa.

Mã thử nghiệm có thể cần một số giải thích. Chúng tôi quan tâm đến việc trình biên dịch có khởi tạo biến mà không có mã người dùng thực sự gọi hàm tạo hay không. Chúng tôi muốn kiểm tra xem đối tượng có được khởi tạo hay không. Bây giờ nếu chúng ta chỉ cần tạo một đối tượng trong một hàm, nó có thể xảy ra để đánh một vị trí bộ nhớ không bị ảnh hưởng và đã chứa các số 0. Chúng tôi muốn phân biệt sự may mắn từ thành công, vì vậy chúng tôi xác định một biến trong một hàm và gọi hàm hai lần. Trong lần chạy đầu tiên, nó sẽ in nội dung bộ nhớ và bắt buộc thay đổi. Trong cuộc gọi thứ hai đến hàm, khi dấu vết ngăn xếp giống nhau, biến sẽ được giữ chính xác ở cùng vị trí bộ nhớ. Nếu nó được khởi tạo, nó sẽ được đặt thành 0, nếu không nó sẽ giữ cùng một giá trị biến cũ ở chính xác cùng một vị trí.

Trong mỗi lần chạy thử, giá trị đầu tiên được in là giá trị khởi tạo (nếu nó đã được khởi tạo) hoặc giá trị ở vị trí bộ nhớ đó, trong một số trường hợp là 0. Giá trị thứ hai chỉ là một thử nghiệm token đại diện cho giá trị tại vị trí bộ nhớ sau khi thay đổi nó theo cách thủ công. Giá trị thứ ba xuất phát từ lần chạy thứ hai của hàm. Nếu biến được khởi tạo, nó sẽ giảm về 0. Nếu đối tượng không được khởi tạo, bộ nhớ của nó sẽ giữ nội dung cũ.

+0

Bạn có biết tại sao khi bạn chạy thử nghiệm () đầu ra là ??, 5, 5, 5 thay vì ??, 5, ??, 5 không phải là đối tượng Foo (x) đi ra khỏi phạm vi khi test_internal trả về ? Tương tự cho thử nghiệm ()? – user1084113

+0

@ user1084113: Bởi vì nó là hành vi không xác định, bạn có thể nhận được một trong hai, nhưng kiểm tra lạm dụng kiến ​​thức về cách trình biên dịch/kiến ​​trúc thường sử dụng ngăn xếp. Về cơ bản, khi một hàm được nhập, nó lấy một phần của ngăn xếp cho các biến nội bộ của nó sau đó phát hành khi hàm kết thúc (tùy thuộc vào quy ước gọi nó có thể là người gọi hoặc hàm cập nhật con trỏ ngăn xếp). Cuộc gọi thứ hai tới hàm sử dụng cùng một khối bộ nhớ cho các biến cục bộ, được đặt theo cùng thứ tự. 'X' được uninitialized, có giá trị được gán trong lần chạy cuối cùng của hàm. –

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