Sự cố khi xóa thông qua hàm hủy


8

Trong chương trình sau đây, tôi dự định sao chép char* linenội dung từ đối tượng này sang đối tượng khác thông qua strcpy. Tuy nhiên, khi chương trình kết thúc, công cụ phá hủy obj2hoạt động tốt nhưng lại objgặp sự cố. gdb hiển thị các địa chỉ khác nhau của linecả hai đối tượng.

class MyClass {
        public:
                char *line;
                MyClass() {
                        line = 0;
                }
                MyClass(const char *s) {
                        line = new char[strlen(s)+1];
                        strcpy(line, s);
                }
                ~MyClass() {
                        delete[] line;
                        line = 0;
                }
                MyClass &operator=(const MyClass &other) {
                        delete[] line;
                        line = new char[other.len()+1];
                        strcpy(line, other.line);
                        return *this;
                }
                int len(void) const {return strlen(line);}
};

int main() {
        MyClass obj("obj");
        MyClass obj2 = obj;

Mặc dù bạn sử dụng các chuỗi kết thúc null kiểu C, bạn vẫn lập trình trong C ++.
Một số lập trình viên anh chàng

5
Bạn cần một constructor sao chép quá. Quy tắc ba
ChrisMM

Đó là bởi vì tôi đã được yêu cầu mô phỏng chuỗi sao chép trong c ++ thông qua strcpy
anurag86

2
Chỉ là một bên, một khi bạn thêm một hàm tạo sao chép: MyClass obj1; MyClass obj2 = obj1;vẫn sẽ segfault vì bạn sẽ gọi strlen(obj1.line)đó là strlen(NULL). Như sẽ MyClass obj1; obj1.len();.
Bill Lynch

2
Ngoài ra hành vi không xác định: MyClass obj1; obj1.len(); Đó là hành vi không xác định để gọi strlentrên một con trỏ null.
PaulMcKenzie

Câu trả lời:


13

Với

MyClass obj2 = obj;

bạn không có nhiệm vụ, bạn có bản sao xây dựng . Và bạn không tuân theo quy tắc ba, năm hoặc không vì bạn không có công cụ tạo bản sao, do đó, người tạo mặc định sẽ chỉ sao chép con trỏ.

Điều đó có nghĩa là sau này bạn có hai đối tượng có linecon trỏ đều trỏ đến cùng một bộ nhớ. Điều đó sẽ dẫn đến hành vi không xác định khi một trong các đối tượng bị phá hủy khi nó rời khỏi đối tượng khác với một con trỏ không hợp lệ.

Giải pháp ngây thơ là thêm một hàm tạo sao chép, bản sao sâu của chính chuỗi đó, tương tự như những gì toán tử gán của bạn đang làm.

Một giải pháp tốt hơn sẽ là sử dụng std::stringthay thế cho chuỗi của bạn và tuân theo quy tắc về số không.


4

Bạn cần tạo một constructor sao chép. Điều này phải làm theo quy tắc 3/5 . Bạn đang tạo obj2, có nghĩa là một hàm tạo sao chép được gọi, không phải toán tử gán sao chép.

Vì bạn không có trình tạo bản sao, nên bản sao "nông" được tạo. Điều này có nghĩa linelà được sao chép theo giá trị. Vì nó là một con trỏ, cả hai objobj2đều trỏ đến cùng một bộ nhớ. Hàm hủy đầu tiên được gọi và xóa bộ nhớ đó. Hàm tạo thứ hai được gọi và xóa kép xảy ra, gây ra lỗi phân đoạn của bạn.

class MyClass {
public:
  char *line = nullptr;
  std::size_t size_ = 0;  // Need to know the size at all times, can't 
                          // rely on null character existing
  const std::size_t MAX_SIZE = 256;  // Arbitrarily chosen value
  MyClass() { }
  MyClass(const char *s) : size_(strlen(s)) {
    if (size_ > MAX_SIZE) size_ = MAX_SIZE;
    line = new char[size_];
    strncpy(line, s, size_ - 1);  // 'n' versions are better
    line[size_ - 1] = '\0';
  }
  MyClass(const MyClass& other) : size_(other.size_) {  // Copy constructor
    line = new char[size_ + 1];
    strncpy(line, other.line, size_);
    line[size_] = '\0';
  }
  ~MyClass() {
    delete[] line;
    line = nullptr;
  }
  MyClass& operator=(const MyClass &other) {
    if (line == other.line) return *this;  // Self-assignment guard
    size_ = other.size_;
    delete[] line;
    line = new char[other.size_ + 1];
    strncpy(line, other.line, size_);
    line[size_] = '\0';
    return *this;
  }
  int len(void) const { return size_; }
};

Khi giao dịch với C-String, bạn hoàn toàn không thể mất ký tự null. Vấn đề là nó cực kỳ dễ mất. Bạn cũng đang thiếu một người bảo vệ tự gán trong toán tử gán bản sao của mình. Điều đó có thể đã dẫn đến việc bạn vô tình bắt cóc một đối tượng. Tôi đã thêm một size_thành viên và được sử dụng strncpy()thay strcpy()vì bởi vì việc chỉ định số lượng ký tự tối đa là vô cùng quan trọng trong trường hợp mất ký tự null. Nó sẽ không ngăn chặn thiệt hại, nhưng nó sẽ giảm thiểu nó.

Có một số thứ khác mà tôi thích bằng cách sử dụng Khởi tạo thành viên mặc định (kể từ C ++ 11) và sử dụng danh sách khởi tạo thành viên của nhà xây dựng . Rất nhiều thứ này trở nên không cần thiết nếu bạn có thể sử dụng std::string. C ++ có thể là "C với các lớp" nhưng thật đáng để dành thời gian để thực sự khám phá những gì ngôn ngữ cung cấp.

Một cái gì đó mà một hàm tạo và hàm hủy sao chép hoạt động cho phép chúng ta làm là đơn giản hóa toán tử gán gán sao chép của chúng ta bằng cách sử dụng "thành ngữ sao chép và hoán đổi."

#include <utility>

MyClass& operator=(MyClass tmp) { // Copy by value now
  std::swap(*this, tmp);
  return *this;
}

Liên kết để giải thích .


2
Một giải pháp tốt hơn sẽ là một triển khai sử dụng thành ngữ sao chép và hoán đổi.
Philomath

1
Đã học một điều mới, và tôi thích nó. Tôi sẽ thêm một chút.
sweenish

Cảm ơn bản sao bằng cách sử dụng ví dụ hoán đổi
anurag86
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.