Làm thế nào để xử lý các trường hợp thất bại trong hàm tạo của lớp C ++?


21

Tôi có một lớp CPP mà hàm tạo của nó thực hiện một số thao tác. Một số hoạt động có thể thất bại. Tôi biết rằng các nhà xây dựng không trả lại bất cứ điều gì.

Câu hỏi của tôi là

  1. Có được phép thực hiện một số thao tác khác để khởi tạo thành viên trong hàm tạo không?

  2. Có thể nói với chức năng gọi rằng một số hoạt động trong hàm tạo đã bị lỗi không?

  3. Tôi có thể new ClassName()trả về NULL nếu một số lỗi xảy ra trong hàm tạo không?


22
Bạn có thể ném một ngoại lệ từ bên trong hàm tạo. Đó là một mô hình hoàn toàn hợp lệ.
Andy

1
Có lẽ bạn nên xem qua một số mô hình sáng tạo của GoF . Tôi đề nghị các mô hình nhà máy.
SpaceTrucker

2
Một ví dụ phổ biến của # 1 là xác nhận dữ liệu. IE nếu bạn có một lớp Square, với một hàm tạo có một tham số, độ dài của một cạnh, bạn muốn kiểm tra xem giá trị đó có lớn hơn 0.
David nói Phục hồi

1
Đối với câu hỏi đầu tiên, hãy để tôi cảnh báo bạn rằng các hàm ảo có thể hành xử vô tình trong các hàm tạo. Tương tự với bộ giải cấu trúc. Coi chừng gọi như vậy.

1
# 3 - Tại sao bạn muốn trả lại NULL? Một trong những lợi ích của OO là KHÔNG phải kiểm tra giá trị trả về. Chỉ cần nắm bắt () các ngoại lệ tiềm năng thích hợp.
MrWonderful

Câu trả lời:


42
  1. Vâng, mặc dù một số tiêu chuẩn mã hóa có thể cấm nó.

  2. Vâng. Cách được đề nghị là ném một ngoại lệ. Ngoài ra, bạn có thể lưu trữ thông tin lỗi bên trong đối tượng và cung cấp các phương thức để truy cập thông tin này.

  3. Không.


4
Trừ khi đối tượng vẫn ở trạng thái hợp lệ mặc dù một số đối số của hàm tạo không đáp ứng yêu cầu và do đó được đánh dấu là lỗi, 2) thực sự không được khuyến khích thực hiện. Sẽ tốt hơn khi một đối tượng tồn tại ở trạng thái hợp lệ hoặc hoàn toàn không tồn tại.
Andy

@DavidPacker Đồng ý, xem tại đây: stackoverflow.com/questions/77639/NH Nhưng một số nguyên tắc mã hóa cấm các trường hợp ngoại lệ, gây rắc rối cho các nhà xây dựng.
Sebastian Redl

Bằng cách nào đó tôi đã đưa cho bạn một câu trả lời cho câu trả lời đó, Sebastian. Hấp dẫn. : D
Andy

10
@ooxi Không, không phải vậy. Mới bị ghi đè của bạn được gọi để phân bổ bộ nhớ, nhưng cuộc gọi đến hàm tạo được trình biên dịch thực hiện sau khi toán tử của bạn quay trở lại, điều đó có nghĩa là bạn không bị bắt lỗi. Đó là giả định mới được gọi là tất cả; nó không dành cho các đối tượng được phân bổ ngăn xếp, mà hầu hết là các đối tượng.
Sebastian Redl

1
Đối với # 1, RAII là một ví dụ phổ biến trong đó có thể yêu cầu nhiều hơn trong hàm tạo.
Eric

20

Bạn có thể tạo một phương thức tĩnh thực hiện phép tính và trả về một đối tượng trong trường hợp thành công hoặc không phải là trường hợp thất bại.

Tùy thuộc vào cách thực hiện việc xây dựng đối tượng này, có thể tốt hơn là tạo một đối tượng khác cho phép xây dựng các đối tượng theo phương thức không tĩnh.

Gọi một nhà xây dựng gián tiếp thường được gọi là "nhà máy".

Điều này cũng sẽ cho phép bạn trả về một đối tượng null, đây có thể là một giải pháp tốt hơn so với trả về null.


Cảm ơn bạn @null! Thật không may, không thể chấp nhận hai câu trả lời ở đây :( Nếu không thì tôi cũng đã chấp nhận câu trả lời này !! Cảm ơn một lần nữa!
MayurK

@MayurK không phải lo lắng, câu trả lời được chấp nhận không phải là để đánh dấu các câu trả lời đúng, nhưng một trong đó làm việc cho bạn.
null

3
@null: Trong C ++, bạn không thể quay lại NULL. Chẳng hạn, trong int foo() { return NULLthực tế bạn sẽ trả về 0(không), một đối tượng nguyên. Trong std::string foo() { return NULL; }bạn vô tình gọi std::string::string((const char*)NULL)đó là Hành vi không xác định (NULL không trỏ đến chuỗi kết thúc \ 0).
MSalters

3
std :: tùy chọn có thể là một cách xa nhưng bạn luôn có thể sử dụng boost :: tùy chọn nếu bạn muốn đi theo cách đó.
Sean Burton

1
@Vld: Trong C ++, các đối tượng không bị hạn chế đối với các loại lớp. Và với lập trình chung, không có gì lạ khi kết thúc với các nhà máy int. Eg std::allocator<int>là một nhà máy hoàn hảo lành mạnh.
MSalters

5

@SebastianRedl đã đưa ra các câu trả lời đơn giản, trực tiếp, nhưng một số giải thích thêm có thể hữu ích.

TL; DR = có một quy tắc kiểu để giữ cho các nhà xây dựng đơn giản, có lý do cho nó, nhưng những lý do đó chủ yếu liên quan đến một kiểu mã hóa lịch sử (hoặc đơn giản là xấu). Việc xử lý các trường hợp ngoại lệ trong các hàm tạo được xác định rõ và các hàm hủy sẽ vẫn được gọi cho các biến và thành viên cục bộ được xây dựng đầy đủ, điều đó có nghĩa là không nên có bất kỳ vấn đề nào trong mã C ++ thành ngữ. Quy tắc kiểu vẫn tồn tại, nhưng thông thường đó không phải là vấn đề - không phải tất cả các khởi tạo phải nằm trong hàm tạo và đặc biệt không nhất thiết phải là hàm tạo đó.


Đó là một quy tắc kiểu chung mà các nhà xây dựng nên thực hiện ở mức tối thiểu tuyệt đối có thể để thiết lập trạng thái hợp lệ được xác định. Nếu việc khởi tạo của bạn phức tạp hơn, nó sẽ được xử lý bên ngoài hàm tạo. Nếu không có giá trị khởi tạo giá rẻ mà nhà xây dựng của bạn có thể thiết lập, bạn nên làm suy yếu những kẻ xâm phạm được lớp của bạn thi hành để thêm một giá trị. Ví dụ: nếu phân bổ dung lượng lưu trữ cho lớp của bạn để quản lý quá đắt, hãy thêm trạng thái chưa được phân bổ nhưng chưa có, vì tất nhiên có các trạng thái trường hợp đặc biệt như null không bao giờ gây ra bất kỳ vấn đề nào. À.

Mặc dù phổ biến, chắc chắn ở dạng cực đoan này, nó rất xa. Cụ thể, như lời mỉa mai của tôi chỉ ra, tôi ở trong trại nói rằng sự bất biến yếu gần như luôn luôn là một cái giá quá cao. Tuy nhiên, có những lý do đằng sau quy tắc phong cách, và có nhiều cách để có cả các hàm tạo tối thiểu các bất biến mạnh.

Những lý do liên quan đến dọn dẹp hủy diệt tự động, đặc biệt là trong trường hợp ngoại lệ. Về cơ bản, phải có một điểm được xác định rõ khi trình biên dịch trở thành trách nhiệm gọi các hàm hủy. Trong khi bạn vẫn đang trong một cuộc gọi của nhà xây dựng, đối tượng không nhất thiết phải được xây dựng đầy đủ, vì vậy việc gọi hàm hủy cho đối tượng đó là không hợp lệ. Do đó, trách nhiệm phá hủy đối tượng chỉ chuyển đến trình biên dịch khi hàm tạo hoàn thành thành công. Điều này được gọi là RAII (Phân bổ tài nguyên là khởi tạo) không thực sự là tên tốt nhất.

Nếu một ngoại lệ xảy ra bên trong hàm tạo, bất kỳ thứ gì được xây dựng một phần cần phải được làm sạch rõ ràng, thường là trong a try .. catch.

Tuy nhiên, các thành phần của đối tượng đã được xây dựng thành công là trách nhiệm của trình biên dịch. Điều này có nghĩa là trong thực tế, nó không thực sự là một vấn đề lớn. ví dụ

classname (args) : base1 (args), member2 (args), member3 (args)
{
}

Cơ thể của nhà xây dựng này là trống rỗng. Miễn là các nhà xây dựng cho base1, member2member3ngoại lệ an toàn, không có gì phải lo lắng. Ví dụ: nếu hàm tạo của member2ném, hàm tạo đó chịu trách nhiệm tự dọn dẹp. Cơ sở base1đã được xây dựng hoàn chỉnh, do đó, hàm hủy của nó sẽ được gọi tự động. member3thậm chí không bao giờ được xây dựng một phần, vì vậy không cần dọn dẹp.

Ngay cả khi có một cơ thể, các biến cục bộ đã được xây dựng đầy đủ trước khi ngoại lệ được ném sẽ tự động bị hủy, giống như bất kỳ hàm nào khác. Các cơ quan xây dựng xử lý các con trỏ thô hoặc "sở hữu" một loại trạng thái ẩn (được lưu trữ ở nơi khác) - thường có nghĩa là một lệnh gọi hàm bắt đầu / thu nhận phải được khớp với một lệnh gọi kết thúc / giải phóng - có thể gây ra các vấn đề an toàn ngoại lệ, nhưng vấn đề thực sự ở đó không thể quản lý tài nguyên đúng cách thông qua một lớp. Ví dụ: nếu bạn thay thế các con trỏ thô bằng unique_ptrtrong hàm tạo, hàm hủy unique_ptrsẽ được gọi tự động nếu cần.

Vẫn còn những lý do khác mà mọi người đưa ra cho việc ưu tiên các nhà xây dựng tối thiểu. Một là đơn giản vì quy tắc kiểu tồn tại, nhiều người cho rằng các cuộc gọi của nhà xây dựng là rẻ. Một cách để có điều đó, nhưng vẫn có những bất biến mạnh, là có một lớp nhà máy / nhà xây dựng riêng biệt có các bất biến suy yếu thay vào đó, và thiết lập giá trị ban đầu cần thiết bằng cách sử dụng (có thể nhiều) các lệnh gọi hàm thành viên bình thường. Khi bạn có trạng thái ban đầu bạn cần, hãy chuyển đối tượng đó làm đối số cho hàm tạo cho lớp với các bất biến mạnh. Điều đó có thể "đánh cắp can đảm" của đối tượng yếu bất biến - di chuyển ngữ nghĩa - đó là một noexcepthoạt động rẻ tiền (và thường ).

Và tất nhiên bạn có thể gói nó trong một make_whatever ()hàm, vì vậy những người gọi hàm đó không bao giờ cần phải xem thể hiện của lớp suy yếu-bất biến.


Đoạn mà bạn viết "Trong khi bạn vẫn đang trong một lệnh gọi của hàm tạo, đối tượng không nhất thiết phải được xây dựng đầy đủ, do đó, không hợp lệ để gọi hàm hủy cho đối tượng đó. Do đó, trách nhiệm hủy bỏ đối tượng chỉ chuyển đến trình biên dịch khi hàm tạo hoàn thành thành công "thực sự có thể sử dụng một bản cập nhật liên quan đến các hàm tạo. Đối tượng được xây dựng đầy đủ khi bất kỳ hàm tạo có nguồn gốc nhất nào kết thúc và hàm hủy sẽ được gọi nếu có ngoại lệ xảy ra bên trong hàm tạo ủy nhiệm.
Ben Voigt

Do đó, hàm tạo "làm tối thiểu" có thể là riêng tư và hàm "make_whthing ()" có thể là một hàm tạo khác gọi hàm riêng.
Ben Voigt

Đây không phải là định nghĩa của RAII mà tôi quen thuộc. Sự hiểu biết của tôi về RAII là cố ý có được một tài nguyên trong (và chỉ trong) hàm tạo của đối tượng và giải phóng nó trong hàm hủy của nó. Theo cách này, đối tượng có thể được sử dụng trên ngăn xếp để tự động quản lý việc thu thập và giải phóng các tài nguyên mà nó gói gọn. Ví dụ kinh điển là một khóa có được một mutex khi được xây dựng và giải phóng nó khi bị phá hủy.
Eric

1
@Eric - Vâng, đó là thực hành hoàn toàn tiêu chuẩn - một thực hành tiêu chuẩn thường được gọi là RAII. Không chỉ riêng tôi mà kéo dài định nghĩa - thậm chí là Stroustrup, trong một số cuộc nói chuyện. Đúng, RAII là về việc liên kết vòng đời tài nguyên với vòng đời đối tượng, mô hình tinh thần là quyền sở hữu.
Steve314

1
@Eric - trả lời trước đó đã bị xóa vì chúng được giải thích xấu. Dù sao, bản thân các đối tượng là tài nguyên có thể được sở hữu. Mọi thứ nên có một chủ sở hữu, trong một chuỗi ngay đến mainhàm hoặc các biến tĩnh / toàn cục. Một đối tượng được phân bổ sử dụng new, không thuộc sở hữu cho đến khi bạn gán trách nhiệm đó, nhưng con trỏ thông minh sở hữu các đối tượng được phân bổ heap mà chúng tham chiếu và các thùng chứa sở hữu cấu trúc dữ liệu của chúng. Chủ sở hữu có thể chọn xóa sớm, người hủy chủ sở hữu chịu trách nhiệm cuối cùng.
Steve314
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.