Ghi đè an toàn các hàm ảo C ++


100

Tôi có một lớp cơ sở với một hàm ảo và tôi muốn ghi đè hàm đó trong một lớp dẫn xuất. Có cách nào để trình biên dịch kiểm tra xem hàm tôi đã khai báo trong lớp dẫn xuất có thực sự ghi đè một hàm trong lớp cơ sở không? Tôi muốn thêm một số macro hoặc thứ gì đó đảm bảo rằng tôi không vô tình khai báo một hàm mới, thay vì ghi đè hàm cũ.

Lấy ví dụ sau:

class parent {
public:
  virtual void handle_event(int something) const {
    // boring default code
  }
};

class child : public parent {
public:
  virtual void handle_event(int something) {
    // new exciting code
  }
};

int main() {
  parent *p = new child();
  p->handle_event(1);
}

Ở đây parent::handle_event()được gọi thay vì child::handle_event(), bởi vì phương thức con bỏ lỡ phần constkhai báo và do đó khai báo một phương thức mới. Đây cũng có thể là lỗi đánh máy trong tên hàm hoặc một số khác biệt nhỏ trong các loại tham số. Nó cũng có thể dễ dàng xảy ra nếu giao diện của lớp cơ sở thay đổi và ở đâu đó một số lớp dẫn xuất không được cập nhật để phản ánh sự thay đổi.

Có cách nào để tránh vấn đề này, bằng cách nào đó tôi có thể nói với trình biên dịch hoặc một số công cụ khác để kiểm tra điều này giúp tôi không? Bất kỳ cờ trình biên dịch hữu ích nào (tốt nhất là cho g ++)? Làm thế nào để bạn tránh những vấn đề này?


2
Câu hỏi tuyệt vời, tôi đã cố gắng tìm ra lý do tại sao hàm lớp con của tôi không được gọi kể từ một giờ nay!
Akash Mankar

Câu trả lời:


89

Vì g ++ 4.7 nó hiểu overridetừ khóa C ++ 11 mới :

class child : public parent {
    public:
      // force handle_event to override a existing function in parent
      // error out if the function with the correct signature does not exist
      void handle_event(int something) override;
};

@hirschhornsalz: Tôi thấy rằng khi bạn triển khai hàm handle_event và bạn thêm ghi đè vào cuối quá trình triển khai hàm, g ++ sẽ báo lỗi; nếu bạn cung cấp triển khai hàm nội tuyến trong khai báo lớp sau từ khóa ghi đè, mọi thứ đều ổn. Tại sao?
h9uest

3
@ h9uest overridephải được sử dụng trong định nghĩa. Triển khai nội tuyến là cả định nghĩa và triển khai, vậy là ok.
Gunther Piez

@hirschhornsalz vâng, tôi gặp lỗi tương tự trên msg của g ++. Tuy nhiên, một lưu ý phụ: cả bạn và thông báo lỗi g ++ đều sử dụng thuật ngữ "định nghĩa lớp" - chúng ta có nên sử dụng "khai báo" (cặp {khai báo, định nghĩa}) không? Bạn đã làm rõ mình trong ngữ cảnh cụ thể này bằng cách nói "định nghĩa & triển khai", nhưng tôi chỉ thắc mắc tại sao cộng đồng c ++ đột nhiên quyết định thay đổi các điều khoản về các lớp?
h9hest

20

Một cái gì đó như overridetừ khóa của C # không phải là một phần của C ++.

Trong gcc, -Woverloaded-virtualcảnh báo chống ẩn một hàm ảo lớp cơ sở với một hàm có cùng tên nhưng đủ chữ ký khác để nó không ghi đè lên nó. Tuy nhiên, nó sẽ không bảo vệ bạn khỏi việc ghi đè một hàm do viết sai chính tả tên hàm.


2
Đó là nếu bạn tình cờ sử dụng Visual C ++
Steve Rowe 01/02/09

4
Sử dụng Visual C ++ không tạo overridetừ khóa trong C ++; Tuy nhiên, nó có thể có nghĩa là bạn đang sử dụng thứ gì đó có thể biên dịch một số mã nguồn C ++ không hợp lệ. ;)
CB Bailey

3
Thực tế là ghi đè là C ++ không hợp lệ có nghĩa là tiêu chuẩn sai, chứ không phải Visual C ++
Jon

2
@Jon: OK, bây giờ tôi thấy bạn đang lái xe ở đâu. Cá nhân tôi có thể lấy hoặc rời overridechức năng kiểu C # ; Tôi hiếm khi gặp sự cố với việc ghi đè không thành công và chúng tương đối dễ chẩn đoán và sửa chữa. Một người nghĩ rằng tôi sẽ không đồng ý là người dùng VC ++ nên sử dụng nó. Tôi muốn rằng C ++ trông giống như C ++ trên tất cả các nền tảng ngay cả khi một dự án cụ thể không cần phải di động. Nó đáng chú ý là C ++ 0x sẽ có [[base_check]], [[override]][[hiding]]thuộc tính để bạn có thể chọn tham gia vào ghi đè kiểm tra nếu muốn.
CB Bailey

5
Thật thú vị, một vài năm sau khi bình luận sử dụng VC ++ không tạo ra overridemột từ khóa có vẻ như nó đã làm . Chà, không phải là một từ khóa thích hợp mà là một mã định danh đặc biệt trong C ++ 11. Microsoft đẩy đủ cứng để tạo ra một trường hợp đặc biệt này và làm theo định dạng chung cho các thuộc tính và overridelàm cho nó thành tiêu chuẩn :)
David Rodríguez - dribeas

18

Theo như tôi biết, bạn không thể chỉ làm cho nó trừu tượng?

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Tôi nghĩ rằng tôi đã đọc trên www.parashift.com rằng bạn thực sự có thể triển khai một phương pháp trừu tượng. Điều đó có ý nghĩa đối với cá nhân tôi, điều duy nhất nó làm là buộc các lớp con thực hiện nó, không ai nói gì về việc nó không được phép tự thực hiện.


Đến bây giờ tôi mới nhận ra rằng điều này hoạt động trên nhiều thứ hơn là chỉ hủy! Tìm thấy tuyệt vời.
strager,

1
Có một số nhược điểm tiềm ẩn đối với điều này: 1) điều khác mà việc đánh dấu một hoặc nhiều phương thức là trừu tượng làm cho lớp cơ sở không thể khởi tạo được, có thể là một vấn đề nếu đó không phải là một phần của mục đích sử dụng của lớp. 2) lớp cơ sở có thể không phải là của bạn để sửa đổi ngay từ đầu.
Michael Burr,

3
Tôi đồng ý với Michael Burr. Làm cho lớp cơ sở trừu tượng không phải là một phần của câu hỏi. Hoàn toàn hợp lý khi có một lớp cơ sở có chức năng trong một phương thức ảo mà bạn muốn một lớp dẫn xuất ghi đè. Và cũng hợp lý khi muốn bảo vệ chống lại một lập trình viên khác đổi tên hàm trong lớp cơ sở và làm cho lớp dẫn xuất của bạn không còn ghi đè nó nữa. Phần mở rộng "ghi đè" của Microsoft là vô giá trong trường hợp này. Tôi rất muốn thấy nó được thêm vào tiêu chuẩn, vì tiếc là không có cách nào tốt để làm điều này nếu không có nó.
Brian

Điều này cũng ngăn phương thức cơ sở (nói BaseClass::method()) được gọi trong triển khai dẫn xuất (nói DerivedClass::method()), ví dụ: đối với một giá trị mặc định.
Narcolessico

11

Trong MSVC, bạn có thể sử dụng overridetừ khóa CLR ngay cả khi bạn không biên dịch cho CLR.

Trong g ++, không có cách nào trực tiếp thực thi điều đó trong mọi trường hợp; những người khác đã đưa ra câu trả lời tốt về cách sử dụng để xác định sự khác biệt về chữ ký -Woverloaded-virtual. Trong phiên bản tương lai, ai đó có thể thêm cú pháp như __attribute__ ((override))hoặc tương đương bằng cú pháp C ++ 0x.


9

Trong MSVC ++, bạn có thể sử dụng từ khóaoverride

class child : public parent {
public:
  virtual void handle_event(int something) <b>override</b> {
    // new exciting code
  }
};

override hoạt động cho cả mã gốc và mã CLR trong MSVC ++.


5

Làm cho hàm trở nên trừu tượng, để các lớp dẫn xuất không có lựa chọn nào khác ngoài việc ghi đè nó.

@Ray Mã của bạn không hợp lệ.

class parent {
public:
  virtual void handle_event(int something) const = 0 {
    // boring default code
  }
};

Các hàm trừu tượng không thể có các nội dung được xác định trong dòng. Nó phải được sửa đổi để trở thành

class parent {
public:
  virtual void handle_event(int something) const = 0;
};

void parent::handle_event( int something ) { /* do w/e you want here. */ }

3

Tôi sẽ đề nghị một chút thay đổi trong logic của bạn. Nó có thể hoạt động hoặc không, tùy thuộc vào những gì bạn cần hoàn thành.

handle_event () vẫn có thể thực hiện "mã mặc định nhàm chán" nhưng thay vì là ảo, tại điểm bạn muốn nó thực hiện "mã thú vị mới" thì lớp cơ sở gọi một phương thức trừu tượng (tức là phải được ghi đè) phương thức sẽ được cung cấp bởi lớp con cháu của bạn.

CHỈNH SỬA: Và nếu sau này bạn quyết định rằng một số lớp con của bạn không cần phải cung cấp "mã thú vị mới" thì bạn có thể thay đổi trừu tượng thành ảo và cung cấp một lớp cơ sở trống triển khai chức năng "được chèn" đó.


2

Trình biên dịch của bạn có thể có cảnh báo rằng nó có thể tạo ra nếu một hàm lớp cơ sở bị ẩn. Nếu có, hãy kích hoạt nó. Điều đó sẽ bắt các xung đột const và sự khác biệt trong danh sách tham số. Thật không may, điều này sẽ không phát hiện ra lỗi chính tả.

Ví dụ: đây là cảnh báo C4263 trong Microsoft Visual C ++.


1

overrideTừ khóa C ++ 11 khi được sử dụng với khai báo hàm bên trong lớp dẫn xuất, nó buộc trình biên dịch kiểm tra xem hàm được khai báo có thực sự ghi đè lên một hàm lớp cơ sở nào đó hay không. Nếu không, trình biên dịch sẽ báo lỗi.

Do đó, bạn có thể sử dụng công overridecụ xác định để đảm bảo tính đa hình động (ghi đè hàm).

class derived: public base{
public:
  virtual void func_name(int var_name) override {
    // statement
  }
};
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.