Có phải 'mới' và 'xóa' bị phản đối trong C ++ không?


68

Tôi tình cờ thấy một bài kiểm tra liên quan đến khai báo mảng với các kích cỡ khác nhau. Điều đầu tiên tôi nghĩ đến là tôi sẽ cần sử dụng phân bổ động với newlệnh, như thế này:

while(T--) {
   int N;
   cin >> N;
   int *array = new int[N];
   // Do something with 'array'
   delete[] array;
}

Tuy nhiên, tôi thấy rằng một trong những giải pháp cho phép trường hợp sau:

while(T--) {
    int N;
    cin >> N;
    int array[N];
    // Do something with 'array'
}

Sau một chút nghiên cứu tôi đọc rằng g ++ cho phép điều này, nhưng nó khiến tôi suy nghĩ, trong trường hợp nào thì cần phải sử dụng phân bổ động? Hoặc là trình biên dịch dịch điều này là phân bổ động?

Chức năng xóa được bao gồm. Lưu ý, tuy nhiên, câu hỏi ở đây không phải là về rò rỉ bộ nhớ.


54
Ví dụ thứ hai sử dụng một mảngđộ dài thay đổi chưa bao giờ là một phần của C ++. Đối với trường hợp này sử dụng std::vectorthay thế ( std::vector<int> array(N);).
Một số lập trình viên anh chàng

7
Câu trả lời trực tiếp cho câu hỏi của bạn nên là: không, nó không bị phản đối. Mặc dù các phiên bản hiện đại của C ++ cung cấp nhiều tính năng đơn giản hóa việc quản lý quyền sở hữu bộ nhớ (con trỏ thông minh), việc phân bổ các đối tượng bằng cách gọi new OBJtrực tiếp vẫn là một cách phổ biến .
pptaszni

8
Đối với những người khác đang bối rối về lý do tại sao mọi người nói về rò rỉ bộ nhớ, câu hỏi đã được chỉnh sửa để sửa một lỗi không quan trọng đối với câu hỏi
Mike Caron

4
@Mannoj thích sử dụng thuật ngữ Động và Tự động để heap và stack. Thật hiếm khi có thể thực hiện C ++ mà không cần đống và ngăn xếp.
dùng4581602

1
Không có gì đã bị phản đối trong C ++ và sẽ không có gì. Đó là một phần ý nghĩa của C ++.
JoelFan

Câu trả lời:


114

Cả đoạn mã bạn hiển thị là mã C ++ hiện đại, thành ngữ.

newdelete(và new[]delete[]) không bị phản đối trong C ++ và sẽ không bao giờ. Chúng vẫn cách để khởi tạo các đối tượng được phân bổ động. Tuy nhiên, vì bạn phải luôn khớp a newvới delete(và a new[]với a delete[]), chúng được giữ tốt nhất trong các lớp (thư viện) đảm bảo điều này cho bạn. Xem tại sao các lập trình viên C ++ nên giảm thiểu việc sử dụng 'mới'? .

Đoạn mã đầu tiên của bạn sử dụng "trần trụi" new[]và sau đó không bao giờ delete[]là mảng được tạo. Đó là một vấn đề. std::vectorlàm mọi thứ bạn cần ở đây chỉ là tốt Nó sẽ sử dụng một số hình thức newphía sau hậu trường (tôi sẽ không đi sâu vào chi tiết triển khai), nhưng với tất cả những gì bạn phải quan tâm, đó là một mảng động nhưng tốt hơn và an toàn hơn.

Đoạn mã thứ hai của bạn sử dụng "mảng độ dài thay đổi" (VLAs), một tính năng C mà một số trình biên dịch cũng cho phép trong C ++ như một phần mở rộng. Không giống như new, VLAs về cơ bản được phân bổ trên ngăn xếp (một nguồn rất hạn chế). Nhưng quan trọng hơn, chúng không phải là một tính năng C ++ tiêu chuẩn và nên tránh vì chúng không thể mang theo được. Họ chắc chắn không thay thế phân bổ động (tức là heap).


3
Tôi muốn nói thêm rằng mặc dù các VLA không nằm trong tiêu chuẩn chính thức, nhưng chúng được hỗ trợ bởi tất cả các trình biên dịch chính, và do đó, quyết định có nên tránh chúng hay không là vấn đề về phong cách / sở thích hơn là mối quan tâm thực tế đối với tính di động.
Stack Tracer

4
Ngoài ra, hãy nhớ rằng bạn không thể trả lại một mảng hoặc lưu trữ nó ở nơi khác, vì vậy, VLA sẽ không bao giờ tồn tại lâu hơn thời gian thực hiện của chức năng
Ruslan

16
@StackTracer Theo hiểu biết tốt nhất của tôi, MSVC không hỗ trợ VLAs. Và MSVC chắc chắn là một "trình biên dịch chính".
Max Langhof

2
"bạn phải luôn luôn kết hợp một cái mới với xóa" - không phải nếu bạn làm việc với Qt, vì các lớp cơ sở của nó đều có bộ thu gom rác, vì vậy bạn chỉ cần sử dụng newvà quên nó đi, hầu hết thời gian. Đối với các thành phần GUI, khi tiện ích cha mẹ được đóng, các con đi ra khỏi phạm vi và được thu gom rác tự động.
vsz

6
@vsz Ngay cả trong Qt, mỗi cái newvẫn có một kết hợp delete; chỉ là các deletes được thực hiện bởi tiện ích mẹ chứ không phải trong cùng một khối mã như news.
jjramsey

22

Vâng, đối với người mới bắt đầu, new/ deletekhông bị phản đối.

Trong trường hợp cụ thể của bạn, họ không phải là giải pháp duy nhất. Những gì bạn chọn phụ thuộc vào những gì đã ẩn dưới nhận xét "làm gì đó với mảng" của bạn.

Ví dụ thứ 2 của bạn sử dụng tiện ích mở rộng VLA không chuẩn, cố gắng khớp với mảng trên ngăn xếp. Điều này có một số hạn chế nhất định - cụ thể là kích thước hạn chế và không thể sử dụng bộ nhớ này sau khi mảng vượt quá phạm vi. Bạn không thể di chuyển nó ra ngoài, nó sẽ "biến mất" sau khi ngăn xếp thư giãn.

Vì vậy, nếu mục tiêu duy nhất của bạn là thực hiện một tính toán cục bộ và sau đó ném dữ liệu đi, nó thực sự có thể hoạt động tốt. Tuy nhiên, một cách tiếp cận mạnh mẽ hơn sẽ là phân bổ bộ nhớ một cách linh hoạt, tốt nhất là với std::vector. Bằng cách đó, bạn có khả năng tạo không gian cho chính xác bao nhiêu phần tử mà bạn cần dựa trên giá trị thời gian chạy (đó là những gì chúng ta sẽ thực hiện cùng), nhưng nó cũng sẽ tự dọn sạch và bạn có thể di chuyển nó ra của phạm vi này nếu bạn muốn giữ bộ nhớ sử dụng cho lần sau.

Quay trở lại từ đầu, có thể vector sẽ sử dụng newsâu hơn một vài lớp, nhưng bạn không nên quan tâm đến điều đó, vì giao diện mà nó thể hiện vượt trội hơn nhiều. Theo nghĩa đó, sử dụng newdeletecó thể được coi là không khuyến khích.


1
Lưu ý "... vài lớp sâu hơn". Nếu bạn đã thực hiện các thùng chứa của riêng mình, bạn vẫn nên tránh sử dụng newdelete, nhưng nên sử dụng các con trỏ thông minh như thế nào std::unique_pointer.
Tối đa

1
mà thực sự được gọi làstd::unique_ptr
user253751

2
@Max: Các std::unique_ptrlệnh gọi hàm hủy mặc định deletehoặc delete[], có nghĩa là đối tượng sở hữu phải được phân bổ bởi newhoặc bằng new[]mọi cách, các cuộc gọi đã bị ẩn trong std::make_uniqueC ++ 14.
Laurent LA RIZZA

15

Các ví dụ thứ hai của bạn sử dụng mảng có độ dài thay đổi (VLAs), thực sự là một tính năng C99 ( không phải C ++!), Nhưng dù sao cũng được hỗ trợ bởi g ++ .

Xem thêm câu trả lời này .

Lưu ý rằng mảng có chiều dài thay đổi khác với new/ deletevà không "phản đối" chúng theo bất kỳ cách nào.

Xin lưu ý rằng VLAs không phải là ISO C ++.


13

C ++ hiện đại cung cấp những cách dễ dàng hơn để làm việc với phân bổ động. Con trỏ thông minh có thể quan tâm đến việc dọn dẹp sau các trường hợp ngoại lệ (có thể xảy ra ở bất cứ đâu nếu được phép) và trả về sớm, ngay khi các cấu trúc dữ liệu được tham chiếu đi ra khỏi phạm vi, vì vậy có thể có ý nghĩa để sử dụng các thay thế này:

  int size=100;

  // This construct requires the matching delete statement.
  auto buffer_old = new int[size];

  // These versions do not require `delete`:
  std::unique_ptr<int[]> buffer_new (new int[size]);
  std::shared_ptr<int[]> buffer_new (new int[size]); 
  std::vector<int> buffer_new (size);  int* raw_access = buffer_new.data();

Từ C ++ 14 bạn cũng có thể viết

auto buffer_new = std::make_unique<int[]>(size);

điều này trông thậm chí còn đẹp hơn và sẽ ngăn chặn rò rỉ bộ nhớ nếu việc phân bổ thất bại. Từ C ++ 20, bạn sẽ có thể làm nhiều như

auto a = std::make_shared<int[]>(size);

điều này đối với tôi vẫn không biên dịch tại thời điểm viết với gcc 7.4.0. Trong hai ví dụ này, chúng tôi cũng sử dụng autothay vì khai báo kiểu bên trái. Trong mọi trường hợp, sử dụng mảng như bình thường:

buffer_old[0] = buffer_new[0] = 17;

Rò rỉ bộ nhớ từ newvà sự cố từ nhân đôi deletelà điều C ++ đã bị bash trong nhiều năm, là "điểm trung tâm" của tranh luận để chuyển sang các ngôn ngữ khác. Có lẽ tốt hơn để tránh.


Bạn nên tránh các nhà unique/shared_ptrxây dựng có lợi make_unique/shared, không những bạn không phải viết loại được xây dựng hai lần (sử dụng auto) mà còn không có nguy cơ rò rỉ bộ nhớ hoặc tài nguyên nếu việc xây dựng bị hỏng giữa chừng (nếu bạn đang sử dụng loại có thể thất bại)
Simon Hội trưởng

2
make_unique có sẵn với các mảng từ C ++ 14 và make_ Shared chỉ từ C ++ 20. Đây vẫn hiếm khi là một cài đặt mặc định, vì vậy, việc đề xuất std :: make_ Shared <int []> (kích thước) đã tìm cho tôi phần nào trước thời hạn.
Audrius Meskauskas

Đủ công bằng! Tôi không thực sự sử dụng make_shared<int[]>nhiều, khi bạn khá nhiều luôn muốn vector<int>, nhưng tốt để biết.
Simon Buchan

Công cụ sư phạm quá mức, nhưng IIRC, nhà unique_ptrxây dựng không phải là nhà cung cấp, vì vậy đối với các nhà Txây dựng chưa có, vì vậy không có nguy cơ rò rỉ unique_ptr(new int[size])shared_ptrcó những điều sau: "Nếu một ngoại lệ được ném, xóa p được gọi khi T không phải là kiểu mảng, hãy xóa [ ] p khác. ", vì vậy bạn có tác dụng tương tự - rủi ro là cho unique/shared_ptr(new MyPossiblyAllocatingType[size]).
Simon Buchan

3

mớixóa không được phản đối.

Các đối tượng được tạo bởi toán tử mới có thể được truyền bằng tham chiếu. Các đối tượng có thể được xóa bằng cách sử dụng xóa.

mới và xóa là các khía cạnh nền tảng của ngôn ngữ. Sự kiên trì của một đối tượng có thể được quản lý bằng cách sử dụng mới và xóa. Đây chắc chắn sẽ không được phản đối.

Câu lệnh - int mảng [N] là một cách xác định một mảng. Mảng có thể được sử dụng trong phạm vi của khối mã kèm theo. Nó không thể được truyền giống như cách một đối tượng được truyền cho một chức năng khác.


2

Ví dụ đầu tiên cần delete[]ở cuối, hoặc bạn sẽ bị rò rỉ bộ nhớ.

Ví dụ thứ hai sử dụng chiều dài mảng biến không được C ++ hỗ trợ; nó chỉ cho phép biểu thức không đổi cho chiều dài mảng .

Trong trường hợp này, nó rất hữu ích để sử dụng std::vector<>như một giải pháp; bao bọc tất cả các hành động bạn có thể thực hiện trên một mảng thành một lớp mẫu.


3
Bạn có ý nghĩa gì với "cho đến C ++ 11"? Tôi khá chắc chắn, VLAs không bao giờ trở thành một phần của tiêu chuẩn.
churill

nhìn vào tiêu chuẩn của c ++ 14 [tiêu chuẩn c ++ 14] ( isocpp.org/files/ con / N3690.pdf ) trên trang 184 đoạn 8.3.4
dao cạo zig

4
Đó không phải tiêu chuẩn, mà chỉ là một bản nháp và một phần về các mảng thời gian chạy bị ràng buộc "không biến nó thành tiêu chuẩn theo như tôi có thể nói. Cppreference không đề cập đến VLAs ở đó.
churill

1
@zigrazor cppreference.com có một danh sách các liên kết đến các bản nháp gần nhất trước / sau khi công bố từng tiêu chuẩn. Các tiêu chuẩn được công bố không có sẵn miễn phí, nhưng những dự thảo này phải rất gần gũi. Như bạn có thể thấy từ các số tài liệu, bản nháp được liên kết của bạn là một bản nháp hoạt động cũ hơn cho C ++ 14.
quả óc chó

2
@learning_dude Nó không được hỗ trợ bởi các tiêu chuẩn. Câu trả lời là (bây giờ) đúng (mặc dù ngắn). Nó chỉ hoạt động cho bạn vì GCC cho phép nó như một phần mở rộng không chuẩn .
quả óc chó

-4

Cú pháp trông giống như C ++, nhưng thành ngữ tương tự như Algol60 cũ. Nó là phổ biến để có các khối mã như thế này:

read n;
begin
    integer array x[1:n];
    ... 
end;

Ví dụ có thể được viết là:

while(T--) {
    int N;
    cin >> N;
    {
        int array[N];
        // Do something with 'array'
    }
}

Đôi khi tôi bỏ lỡ điều này trong các ngôn ngữ hiện tại;)

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.