Tôi biết chúng ta có thể gọi một cách rõ ràng hàm tạo của một lớp trong C++ bằng toán tử phân giải phạm vi, tức là className::className()
. Tôi đã tự hỏi chính xác nơi tôi sẽ cần phải thực hiện một cuộc gọi như vậy.Tại sao rõ ràng gọi một hàm tạo trong C++
Trả lời
Thông thường, trong một constructor lớp con mà đòi hỏi một số thông số:
class BaseClass
{
public:
BaseClass(const std::string& name) : m_name(name) { }
const std::string& getName() const { return m_name; }
private:
const std::string m_name;
//...
};
class DerivedClass : public BaseClass
{
public:
DerivedClass(const std::string& name) : BaseClass(name) { }
// ...
};
class TestClass :
{
public:
TestClass(int testValue); //...
};
class UniqueTestClass
: public BaseClass
, public TestClass
{
public:
UniqueTestClass()
: BaseClass("UniqueTest")
, TestClass(42)
{ }
// ...
};
... ví dụ.
Ngoài ra, tôi không thấy tiện ích. Tôi chỉ gọi nhà xây dựng ở mã khác khi tôi còn quá nhỏ để biết mình đang làm gì ...
C++ gọi ngầm hàm khởi tạo của các lớp cha khi một cá thể của lớp dẫn xuất được xây dựng, nhưng nó gọi hàm tạo mặc định - trừ khi bạn gọi một hàm riêng cụ thể lớp cha trong danh sách khởi tạo. –
Vâng, trong ví dụ của tôi, tôi đã đảm bảo rằng hàm tạo hợp lệ duy nhất cho BaseClass yêu cầu một số tham số. Tôi không nhớ một trường hợp thực sự đòi hỏi phải gọi hàm dựng mặc định rõ ràng. Có lẽ trong thừa kế ảo? – Klaim
Tôi không nghĩ bạn thường sử dụng nó cho hàm tạo, ít nhất là không theo cách bạn mô tả. Tuy nhiên, bạn sẽ cần nó nếu bạn có hai lớp trong các không gian tên khác nhau. Ví dụ: để chỉ định sự khác biệt giữa hai lớp được tạo này, Xml::Element
và Chemistry::Element
.
Thông thường, tên của lớp được sử dụng với toán tử phân giải phạm vi để gọi hàm trên cha mẹ của lớp được thừa kế. Vì vậy, nếu bạn có một lớp Dog kế thừa từ Animal, và cả hai lớp đó định nghĩa hàm Eat() khác nhau, có thể có trường hợp khi bạn muốn sử dụng phiên bản Animal trên đối tượng Dog gọi là "someDog". Cú pháp C++ của tôi hơi bị rỉ sét, nhưng tôi nghĩ trong trường hợp đó bạn sẽ nói someDog.Animal::Eat()
.
Bạn đôi khi sử dụng một cách rõ ràng một hàm tạo để tạo tạm thời. Ví dụ, nếu bạn có một số lớp học với một constructor:
class Foo
{
Foo(char* c, int i);
};
và một hàm
void Bar(Foo foo);
nhưng bạn không có một Foo xung quanh, bạn có thể làm
Bar(Foo("hello", 5));
này giống như một diễn viên. Thật vậy, nếu bạn có một hàm tạo chỉ nhận một tham số, trình biên dịch C++ sẽ sử dụng hàm tạo đó để thực hiện các phôi ngầm.
Đó là không hợp pháp để gọi hàm tạo trên một đối tượng đã tồn tại. Tức là, bạn không thể làm
Foo foo;
foo.Foo(); // compile error!
bất kể bạn làm gì. Nhưng bạn có thể gọi một hàm tạo mà không cần cấp phát bộ nhớ - đó là những gì vị trí mới là dành cho.
char buffer[sizeof(Foo)]; // a bit of memory
Foo* foo = new(buffer) Foo(); // construct a Foo inside buffer
Bạn cung cấp bộ nhớ mới và tạo đối tượng ở vị trí đó thay vì cấp phát bộ nhớ mới. Việc sử dụng này được coi là xấu xa và hiếm gặp ở hầu hết các loại mã, nhưng phổ biến trong mã cấu trúc nhúng và dữ liệu. Ví dụ: std::vector::push_back
sử dụng kỹ thuật này để gọi hàm tạo bản sao. Bằng cách đó, nó chỉ cần làm một bản sao, thay vì tạo một đối tượng trống và sử dụng toán tử gán.
+1 cho vị trí mới. Thật kỳ lạ, nhưng có thể hữu ích nếu bạn biết bạn đang làm gì. –
"Thật vậy, nếu bạn có một hàm tạo chỉ nhận một tham số, trình biên dịch C++ sẽ sử dụng hàm tạo đó để thực hiện các phôi ngầm." - đó là lý do tại sao rất nhiều người đặt từ khóa rõ ràng trên các nhà xây dựng đơn-arg theo mặc định và chỉ gỡ bỏ nó nếu họ chắc chắn rằng họ muốn ép buộc từ kiểu tham số sang loại lớp. –
-1 Bạn không gọi hàm tạo. Cú pháp là '
Tôi nghĩ rằng các thông báo lỗi cho trình biên dịch báo lỗi C2585 đưa ra lý do tốt nhất lý do tại sao bạn sẽ cần phải thực sự sử dụng toán tử phạm vi độ phân giải trên các nhà xây dựng, và nó với Charlie câu trả lời:
Chuyển đổi từ một lớp học hoặc kiểu cấu trúc dựa trên nhiều thừa kế. Nếu kiểu thừa hưởng cùng một lớp cơ sở nhiều hơn một lần, thì hàm chuyển đổi hoặc toán tử phải sử dụng độ phân giải phạm vi (:) để chỉ định lớp nào được thừa hưởng để sử dụng trong chuyển đổi.
Vì vậy, hãy tưởng tượng bạn có BaseClass, và BaseClassA và BaseClassB đều kế thừa BaseClass và sau đó DerivedClass kế thừa cả BaseClassA và BaseClassB.
Nếu bạn đang thực hiện chuyển đổi hoặc toán tử quá tải để chuyển đổi DerivedClass thành BaseClassA hoặc BaseClassB, bạn sẽ cần xác định hàm khởi tạo nào (tôi đang nghĩ thứ gì đó giống như hàm tạo bản sao, IIRC) để sử dụng trong chuyển đổi.
Nói chung, bạn không gọi trực tiếp cho nhà xây dựng. Toán tử mới gọi nó cho bạn hoặc một lớp con gọi các hàm tạo của lớp cha. Trong C++, lớp cơ sở được bảo đảm để được xây dựng đầy đủ trước khi hàm tạo của lớp dẫn xuất bắt đầu.
Thời gian duy nhất bạn gọi trực tiếp cho nhà xây dựng là trong trường hợp cực kỳ hiếm khi bạn đang quản lý bộ nhớ mà không cần sử dụng mới. Và thậm chí sau đó, bạn không nên làm điều đó. Thay vào đó, bạn nên sử dụng biểu mẫu vị trí của toán tử mới.
Có các trường hợp sử dụng hợp lệ mà bạn muốn hiển thị một trình tạo lớp. Nếu bạn muốn quản lý bộ nhớ của riêng bạn với một trình phân bổ trường, ví dụ, bạn sẽ cần xây dựng hai giai đoạn bao gồm phân bổ và khởi tạo đối tượng.
Cách tiếp cận tôi thực hiện tương tự với nhiều ngôn ngữ khác. Tôi chỉ cần đặt mã xây dựng của mình vào các phương thức công cộng nổi tiếng (Xây dựng(), init(), giống như vậy) và gọi trực tiếp khi cần.
Bạn có thể tạo quá tải của các phương thức này phù hợp với nhà thầu của bạn; các nhà thầu thường xuyên của bạn chỉ cần gọi họ. Đặt các nhận xét lớn trong mã để cảnh báo người khác rằng bạn đang làm điều này để họ không thêm mã xây dựng quan trọng vào địa điểm sai.
Hãy nhớ rằng chỉ có một phương pháp phá hủy bất kể quá tải xây dựng đã được sử dụng, vì vậy hãy làm cho trình phá hủy của bạn mạnh mẽ đối với các thành viên chưa được khởi tạo.
Tôi khuyên bạn không nên cố viết các trình khởi tạo có thể khởi tạo lại. Thật khó để nói với trường hợp bạn đang nhìn vào một vật thể chỉ có rác trong đó vì bộ nhớ chưa được khởi tạo so với thực sự đang giữ dữ liệu thực.
Vấn đề khó khăn nhất đi kèm với các lớp học có phương pháp ảo. Trong trường hợp này trình biên dịch thường cắm vào con trỏ bảng hàm vtable như là một trường ẩn ở đầu lớp. Bạn có thể khởi tạo thủ công con trỏ này, nhưng về cơ bản bạn tùy thuộc vào hành vi của trình biên dịch cụ thể và có khả năng khiến các đồng nghiệp của bạn nhìn bạn buồn cười.
Vị trí mới bị hỏng ở nhiều khía cạnh; trong việc xây dựng/phá hủy mảng là một trường hợp vì vậy tôi có xu hướng không sử dụng nó.
Xem xét chương trình sau.
template<class T>
double GetAverage(T tArray[], int nElements)
{
T tSum = T(); // tSum = 0
for (int nIndex = 0; nIndex < nElements; ++nIndex)
{
tSum += tArray[nIndex];
}
// Whatever type of T is, convert to double
return double(tSum)/nElements;
}
Điều này sẽ gọi hàm tạo mặc định để khởi tạo biến.
Không đúng khi nói rằng bạn có thể gọi trực tiếp cho nhà xây dựng. Tiêu chuẩn rõ ràng có (12.1/1): "Các nhà xây dựng không có tên." Bạn chỉ có thể gọi hàm tạo thông qua các cấu trúc khác, chẳng hạn như dàn diễn viên hàm hoặc vị trí mới. –