Theo tôi, sự nguy hiểm của C ++ có phần cường điệu.
Mối nguy hiểm cơ bản là đây: Mặc dù C # cho phép bạn thực hiện các thao tác con trỏ "không an toàn" bằng unsafe
từ khóa, C ++ (chủ yếu là siêu ký tự của C) sẽ cho phép bạn sử dụng con trỏ bất cứ khi nào bạn cảm thấy thích. Bên cạnh những nguy hiểm thông thường vốn có khi sử dụng con trỏ (giống với C), như rò rỉ bộ nhớ, tràn bộ đệm, con trỏ lơ lửng, v.v., C ++ giới thiệu những cách mới để bạn thực hiện mọi thứ một cách nghiêm túc.
"Sợi dây phụ" này, có thể nói, mà Joel Spolsky đang nói đến, về cơ bản nói đến một điều: viết các lớp học quản lý nội bộ của chính họ, còn được gọi là " Quy tắc 3 " (giờ đây có thể được gọi là Quy tắc của 4 hoặc Quy tắc 5 trong C ++ 11). Điều này có nghĩa là, nếu bạn từng muốn viết một lớp quản lý phân bổ bộ nhớ của riêng mình trong nội bộ, bạn phải biết bạn đang làm gì nếu không chương trình của bạn sẽ có khả năng bị sập. Bạn phải cẩn thận tạo một hàm tạo, sao chép hàm tạo, hàm hủy và toán tử gán, điều này rất dễ gây ra lỗi, thường dẫn đến các sự cố kỳ lạ khi chạy.
TUY NHIÊN , trong lập trình C ++ thực tế hàng ngày, thực sự rất hiếm khi viết một lớp quản lý bộ nhớ của chính nó, vì vậy thật sai lầm khi nói rằng các lập trình viên C ++ luôn cần phải "cẩn thận" để tránh những cạm bẫy này. Thông thường, bạn sẽ chỉ làm một cái gì đó giống như:
class Foo
{
public:
Foo(const std::string& s)
: m_first_name(s)
{ }
private:
std::string m_first_name;
};
Lớp này trông khá gần với những gì bạn làm trong Java hoặc C # - nó không yêu cầu quản lý bộ nhớ rõ ràng (vì lớp thư viện std::string
sẽ tự động xử lý tất cả những thứ đó) và không yêu cầu "Quy tắc 3" nào cả mặc định sao chép constructor và toán tử gán là tốt.
Chỉ khi bạn cố gắng làm một cái gì đó như:
class Foo
{
public:
Foo(const char* s)
{
std::size_t len = std::strlen(s);
m_name = new char[len + 1];
std::strcpy(m_name, s);
}
Foo(const Foo& f); // must implement proper copy constructor
Foo& operator = (const Foo& f); // must implement proper assignment operator
~Foo(); // must free resource in destructor
private:
char* m_name;
};
Trong trường hợp này, có thể khó khăn cho người mới để có được phép gán, hàm hủy và sao chép chính xác. Nhưng đối với hầu hết các trường hợp, không có lý do gì để làm điều này. C ++ giúp dễ dàng tránh việc quản lý bộ nhớ thủ công 99% thời gian bằng cách sử dụng các lớp thư viện như std::string
và std::vector
.
Một vấn đề liên quan khác là quản lý bộ nhớ theo cách thủ công theo cách không tính đến khả năng ngoại lệ bị ném. Giống:
char* s = new char[100];
some_function_which_may_throw();
/* ... */
delete[] s;
Nếu some_function_which_may_throw()
thực sự không có ngoại lệ, bạn sẽ bị rò rỉ bộ nhớ vì bộ nhớ được phân bổ s
sẽ không bao giờ được lấy lại. Nhưng một lần nữa, trong thực tế, điều này hầu như không còn là vấn đề nữa vì cùng một lý do là "Quy tắc 3" thực sự không còn là vấn đề nữa. Rất hiếm (và thường là không cần thiết) để thực sự quản lý bộ nhớ của riêng bạn bằng các con trỏ thô. Để tránh vấn đề trên, tất cả những gì bạn cần làm là sử dụng một std::string
hoặc std::vector
, và hàm hủy sẽ tự động được gọi trong khi giải nén ngăn xếp sau khi ném ngoại lệ.
Vì vậy, một chủ đề chung ở đây là nhiều tính năng C ++ không được kế thừa từ C, như khởi tạo / hủy tự động, xây dựng bản sao và ngoại lệ, buộc lập trình viên phải hết sức cẩn thận khi thực hiện quản lý bộ nhớ thủ công trong C ++. Nhưng một lần nữa, đây chỉ là vấn đề nếu bạn có ý định quản lý bộ nhớ thủ công ngay từ đầu, điều này hầu như không còn cần thiết nữa khi bạn có các thùng chứa tiêu chuẩn và con trỏ thông minh.
Vì vậy, theo tôi, trong khi C ++ cung cấp cho bạn rất nhiều dây, thì hầu như không cần thiết phải sử dụng nó để treo cổ và những cạm bẫy mà Joel đang nói đến là rất dễ tránh trong C ++ hiện đại.
Your questions should be reasonably scoped. If you can imagine an entire book that answers your question, you’re asking too much.
. Tôi tin rằng điều này đủ điều kiện như một câu hỏi như vậy ...