Tại sao chúng ta đặt các chức năng thành viên tư nhân trong các tiêu đề?


16

Câu trả lời cho lý do tại sao chúng ta đặt các biến thành viên riêng trong các tiêu đề C ++ là kích thước của lớp phải được biết tại các điểm mà các thể hiện được khai báo để trình biên dịch có thể tạo mã di chuyển thích hợp về ngăn xếp.

Tại sao chúng ta cần đưa các thành viên tư nhân vào tiêu đề?

Nhưng có lý do nào để khai báo các hàm riêng trong định nghĩa lớp không?

Sự thay thế về cơ bản sẽ là thành ngữ pimpl nhưng không có sự gián tiếp thừa.

Là tính năng ngôn ngữ này nhiều hơn một lỗi lịch sử?

Câu trả lời:


11

Các hàm thành viên riêng có thể virtualvà trong các triển khai chung của C ++ (sử dụng vtable), thứ tự và số lượng các hàm ảo cụ thể được yêu cầu phải được biết bởi tất cả các máy khách của lớp. Điều này áp dụng ngay cả khi một hoặc nhiều chức năng thành viên ảo là private.

Có vẻ như điều này giống như "đặt xe trước ngựa", bởi vì các lựa chọn triển khai trình biên dịch không ảnh hưởng đến đặc tả ngôn ngữ. Tuy nhiên, trong thực tế, ngôn ngữ C ++ được phát triển cùng lúc với triển khai hoạt động ( Cfront ), sử dụng vtables.


4
"Các lựa chọn thực hiện không nên ảnh hưởng đến ngôn ngữ" gần như trái ngược hoàn toàn với câu thần chú C ++. Đặc tả kỹ thuật không bắt buộc bất kỳ triển khai cụ thể nào, nhưng có rất nhiều quy tắc được tạo riêng để phục vụ các yêu cầu của lựa chọn triển khai đặc biệt hiệu quả.
Ben Voigt

Điều này nghe có vẻ rất hợp lý. Có một nguồn trên này?
Praxeolitic

@Praxeolitic: Bạn có thể đọc về bảng phương thức ảo .
Greg Hewgill

1
@Praxeolitic - tìm một bản sao cũ của 'Hướng dẫn giới thiệu C ++ được chú thích' ( stroustrup.com/arm.html ) - các tác giả nói về các chi tiết triển khai khác nhau và cách nó định hình ngôn ngữ.
Michael Kohne

Tôi không chắc điều này thực sự trả lời câu hỏi. Đối với tôi, nó chỉ giải quyết một khía cạnh cụ thể - mặc dù tốt - và bỏ lại phần còn lại: Tại sao chúng ta cần đặt các chức năng thành viên riêng không ảo trong các tiêu đề? Chắc chắn, hoàn toàn cho sự nhất quán / đơn giản là một lý lẽ - nhưng lý tưởng nhất là chúng ta cũng có một lý do cơ học và / hoặc triết học. Vì vậy, tôi đã thêm một câu trả lời mà tôi tin rằng giải thích đây là một lựa chọn thiết kế rất có chủ ý với các hiệu ứng rất có lợi.
gạch dưới

13

Nếu bạn cho phép các phương thức được thêm vào một lớp ngoài định nghĩa của nó, chúng có thể được thêm vào bất cứ nơi nào , trong bất kỳ tệp nào, bởi bất kỳ ai.

Điều đó sẽ ngay lập tức cung cấp cho tất cả các mã khách hàng quyền truy cập tầm thường vào các thành viên dữ liệu riêng tư và được bảo vệ.

Khi bạn đã hoàn thành định nghĩa lớp, không có cách nào để đánh dấu một số tệp là được tác giả ban phước đặc biệt để mở rộng nó - chỉ có các đơn vị dịch phẳng. Vì vậy, cách hợp lý duy nhất để nói với trình biên dịch rằng một tập hợp các phương thức cụ thể là chính thức, hoặc được ban phước bởi tác giả lớp, là khai báo chúng bên trong lớp.


Lưu ý rằng chúng ta có quyền truy cập trực tiếp vào bộ nhớ trong C ++, điều đó có nghĩa là việc tạo ra một loại bóng với bố cục bộ nhớ giống như lớp của bạn, thêm các phương thức của riêng tôi (hoặc chỉ công khai tất cả dữ liệu) và reinterpret_cast. Hoặc tôi có thể tìm mã của chức năng riêng tư của bạn hoặc tháo rời nó. Hoặc tra cứu địa chỉ chức năng trong bảng ký hiệu và gọi trực tiếp.

Những người xác định truy cập này không cố gắng ngăn chặn các cuộc tấn công này, vì điều đó là không thể. Chúng chỉ cho biết làm thế nào một lớp được sử dụng.


1
Định nghĩa lớp có thể đề cập đến một thực thể chứa hàm được ẩn khỏi mã máy khách. Ngoài ra, điều này có thể được đảo ngược và một định nghĩa lớp truyền thống hoàn chỉnh được ẩn khỏi mã máy khách có thể chỉ định một giao diện hiển thị chỉ mô tả các thành viên công cộng và có thể tiết lộ kích thước của nó.
Praxeolitic

1
Chắc chắn, nhưng mô hình biên dịch không cung cấp một cách hợp lý để ngăn chặn mã máy khách thay đổi phiên bản riêng của thùng chứa ẩn hoặc giao diện hiển thị khác với các bộ truy cập bổ sung.
Vô dụng

Đó là một điểm tốt, nhưng điều này không đúng với định nghĩa của tất cả các hàm thành viên không có trong tiêu đề?
Praxeolitic

1
Tất cả các chức năng thành viên được khai báo bên trong lớp mặc dù. Vấn đề là bạn không thể thêm các hàm thành viên mới mà ít nhất được khai báo trong định nghĩa lớp (và quy tắc một định nghĩa sẽ ngăn chặn nhiều định nghĩa).
Vô dụng

Sau đó, những gì về một thùng chứa các chức năng riêng quy tắc?
Praxeolitic

8

Câu trả lời được chấp nhận giải thích điều này cho các chức năng riêng tư ảo , nhưng điều đó chỉ trả lời một khía cạnh cụ thể của câu hỏi, hạn chế hơn đáng kể so với những gì OP yêu cầu. Vì vậy, chúng ta cần phải viết lại: Tại sao chúng ta bắt buộc phải khai báo các hàm riêng không ảo trong các tiêu đề?

Một câu trả lời khác gọi thực tế là các lớp phải được khai báo trong một khối - sau đó chúng được niêm phong và không thể thêm vào. Đó là những gì bạn đang làm bằng cách bỏ qua để khai báo một phương thức riêng tư trong tiêu đề sau đó cố gắng xác định nó ở nơi khác. Điểm hay. Tại sao một số người dùng của lớp có thể tăng nó theo cách mà những người dùng khác không thể quan sát? Các phương thức riêng tư là một phần của nó và không loại trừ khỏi điều này. Nhưng sau đó bạn hỏi tại sao chúng được bao gồm, và nó có vẻ hơi tự nhiên. Tại sao người dùng lớp phải biết về họ? Nếu chúng không hiển thị, người dùng không thể thêm bất kỳ và xin chào.

Vì vậy, tôi muốn cung cấp một câu trả lời rằng, thay vì chỉ bao gồm các phương thức riêng tư theo mặc định, cung cấp các điểm cụ thể có lợi cho việc hiển thị chúng cho người dùng. Một lý do cơ học cho các chức năng riêng tư không ảo yêu cầu khai báo công khai được đưa ra trong Herb Sutter's GotW # 100 về thành ngữ Pimpl như là một phần của lý do căn bản của nó. Tôi sẽ không nói về Pimpl ở đây, vì tôi chắc rằng tất cả chúng ta đều biết về nó. Nhưng đây là bit có liên quan:

Trong C ++, khi mọi thứ trong định nghĩa lớp tệp tiêu đề thay đổi, tất cả người dùng của lớp đó phải được biên dịch lại - ngay cả khi thay đổi duy nhất là đối với các thành viên lớp riêng mà người dùng của lớp thậm chí không thể truy cập. Điều này là do mô hình xây dựng của C ++ dựa trên sự bao gồm văn bản và bởi vì C ++ giả định rằng người gọi biết hai điều chính về một lớp có thể bị ảnh hưởng bởi các thành viên tư nhân:

  • Kích thước và bố cục : [của các thành viên và chức năng ảo - tự giải thích và tuyệt vời cho hiệu suất, nhưng không phải tại sao chúng tôi ở đây]
  • Hàm : Mã gọi phải có khả năng giải quyết các cuộc gọi đến các chức năng thành viên của lớp, bao gồm các hàm riêng không thể truy cập quá tải với các hàm không bí mật - nếu chức năng riêng phù hợp hơn, mã gọi sẽ không thể biên dịch. (C ++ đã đưa ra quyết định thiết kế có chủ ý để thực hiện giải quyết quá tải trước khi kiểm tra khả năng truy cập vì lý do an toàn. Ví dụ, người ta cảm thấy rằng việc thay đổi khả năng truy cập của một chức năng từ riêng tư sang công cộng không nên thay đổi ý nghĩa của mã gọi hợp pháp.)

Tất nhiên, Sutter là một nguồn cực kỳ đáng tin cậy với tư cách là thành viên của Ủy ban, vì vậy anh ta biết "một quyết định thiết kế có chủ ý" khi anh ta nhìn thấy. Và ý tưởng yêu cầu tuyên bố công khai các phương thức riêng tư như một cách để tránh thay đổi ngữ nghĩa hoặc vô tình bị hỏng khả năng tiếp cận sau này có lẽ là lý do thuyết phục nhất. Rất may, vì toàn bộ điều này dường như khá vô nghĩa trước đây!


" Nhưng sau đó bạn hỏi tại sao, và câu trả lời đó có vẻ tự nhiên. Một lần nữa, tại sao người dùng của lớp cần biết về họ? " Không, đó không phải là tautological. Người dùng không nhất thiết "cần biết về họ". Điều cần phải xảy ra là bạn phải có khả năng ngăn chặn mọi người mở rộng các lớp học mà không có sự đồng ý của nhà văn lớp. Vì vậy, tất cả các thành viên như vậy phải được tuyên bố trước. Điều này khiến người dùng biết về các thành viên tư nhân chỉ là một hậu quả cần thiết.
Nicol Bolas

1
@NicolBolas Chắc chắn. Đó có lẽ là từ ngữ rất tốt về phía tôi. Tôi có nghĩa là câu trả lời chỉ giải thích khả năng hiển thị của các phương thức riêng tư là kết quả của quy tắc (rất hợp lệ) bao gồm nhiều điều, thay vì cung cấp một lý do về khả năng hiển thị của các phương thức riêng tư cụ thể. Tất nhiên, câu trả lời thực sự là sự kết hợp của Vô dụng, gnasher và của tôi. Tôi chỉ muốn cung cấp một viễn cảnh khác chưa có ở đây.
gạch dưới

4

Có hai lý do để làm điều này.

Đầu tiên, nhận ra rằng trình xác định truy cập là dành cho trình biên dịch và không liên quan khi chạy. Truy cập một thành viên tư nhân ngoài phạm vi là một lỗi biên dịch .

Sự đồng tình

Hãy xem xét một hàm ngắn, một hoặc hai dòng. Nó tồn tại để giảm sự sao chép mã ở nơi khác, điều này cũng có lợi thế là có thể thay đổi cách thuật toán hoặc bất cứ thứ gì khác hoạt động ở một nơi thay vì nhiều (ví dụ thay đổi thuật toán sắp xếp).

Bạn có muốn có một hoặc hai dòng nhanh trong tiêu đề hoặc có nguyên mẫu hàm ở đó cộng với việc triển khai ở đâu đó không? Dễ dàng tìm thấy hơn trong tiêu đề và đối với các hàm ngắn, việc thực hiện riêng biệt sẽ dài hơn nhiều.

Có một lợi thế lớn khác, đó là ...

Hàm nội tuyến

Một chức năng riêng tư có thể có thể được nội tuyến và điều này nhất thiết đòi hỏi nó phải nằm trong tiêu đề. Xem xét điều này:

class A {
  private:
    inline void myPrivateFunction() {
      ...
    }

  public:
    inline void somePublicFunction() {
      myPrivateFunction();
      ...
    }
};

Chức năng riêng tư thể được nội tuyến cùng với chức năng công cộng. Điều đó được thực hiện theo quyết định của nhà soạn nhạc, vì inlinetừ khóa về mặt kỹ thuật là một gợi ý , không phải là một yêu cầu.


Tất cả các hàm được định nghĩa bên trong thân lớp được tự động đánh dấu inline, không có lý do gì để sử dụng từ khóa ở đó.
Ben Voigt

@BenVoigt vậy đó. Tôi đã không nhận ra điều đó và tôi đã sử dụng C ++ bán thời gian khá lâu. Ngôn ngữ đó không bao giờ hết làm tôi ngạc nhiên với những hạt cốm nhỏ như thế.

Tôi không nghĩ rằng một phương thức cần phải nằm trong tiêu đề để được nội tuyến. Nó chỉ cần ở trong cùng một đơn vị biên dịch. Có trường hợp nào có ý nghĩa khi có các đơn vị biên dịch riêng cho một lớp không?
Samuel Danielson

@SamuelDanielson đúng, nhưng một hàm riêng phải nằm trong định nghĩa lớp. Khái niệm "riêng tư" ngụ ý nó là một phần của lớp học. Có thể có một hàm nonmember trong .cpptệp được nội tuyến bởi các hàm thành viên được xác định bên ngoài định nghĩa lớp, nhưng một hàm như vậy sẽ không riêng tư.

Phần chính của câu hỏi là "có bất kỳ lý do nào để khai báo các hàm riêng trong định nghĩa lớp [sic, qua đó ngữ cảnh cho thấy OP thực sự có nghĩa là khai báo lớp]". Bạn đang nói về việc xác định các chức năng riêng tư. Điều này không trả lời câu hỏi. @SamuelDanielson Ngày nay, LTO có nghĩa là các chức năng có thể ở bất cứ đâu trong một dự án và duy trì cơ hội bình đẳng như nhau. Đối với việc tách một lớp thành nhiều đơn vị dịch thuật, trường hợp đơn giản nhất là lớp này chỉ lớn và bạn muốn phân tách về mặt ngữ nghĩa thành nhiều tệp nguồn. Tôi chắc chắn rằng các lớp lớn như vậy không được khuyến khích, nhưng dù sao đi nữa
underscore_d

2

Một lý do khác để có các phương thức riêng tư trong tệp tiêu đề: Có những trường hợp phương thức nội tuyến công khai không nhiều hơn gọi một hoặc một số phương thức riêng tư. Có các phương thức riêng tư trong tiêu đề có nghĩa là một cuộc gọi đến phương thức công khai hoàn toàn có thể được đặt vào mã thực tế của các phương thức riêng tư và nội tuyến không dừng với một cuộc gọi đến phương thức riêng tư. Ngay cả từ một đơn vị biên dịch khác nhau (và các phương thức công khai thường sẽ được gọi từ các đơn vị biên dịch khác nhau).

Tất nhiên, có lý do là trình biên dịch có thể phát hiện các vấn đề với độ phân giải quá tải nếu nó không biết tất cả các phương thức, kể cả các phương thức riêng tư.


Điểm tuyệt vời về nội tuyến! Tôi tưởng tượng điều này đặc biệt phù hợp với stdlib và các thư viện khác với các phương thức được định nghĩa nội tuyến. (nếu không quá nhiều cho mã nội bộ, trong đó LTO có thể làm hầu hết mọi thứ qua các ranh giới TU)
underscore_d

0

Đó là cho phép các chức năng đó truy cập các thành viên tư nhân. Nếu không, bạn cần phải có friendchúng trong tiêu đề.

Nếu bất kỳ chức năng nào có thể truy cập các thành viên riêng của lớp thì riêng tư sẽ vô dụng.


1
Có lẽ tôi nên rõ ràng hơn trong câu hỏi - ý tôi là tại sao ngôn ngữ được thiết kế theo cách này? Tại sao nó phải hoặc tại sao thiết kế này là một tốt?
Praxeolitic
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.