Tại sao C ++ không cho phép bạn lấy địa chỉ của nhà xây dựng?


13

Có một lý do cụ thể rằng điều này sẽ phá vỡ ngôn ngữ về mặt khái niệm hoặc một lý do cụ thể mà điều này là không khả thi về mặt kỹ thuật trong một số trường hợp?

Việc sử dụng sẽ là với nhà điều hành mới.

Chỉnh sửa: Tôi sẽ từ bỏ hy vọng về việc đưa "nhà điều hành mới" và "nhà điều hành mới" của mình thẳng thắn và trực tiếp.

Điểm của câu hỏi là: tại sao các nhà xây dựng đặc biệt ? Tất nhiên hãy nhớ rằng các thông số kỹ thuật ngôn ngữ cho chúng ta biết thế nào là hợp pháp, nhưng không nhất thiết là đạo đức. Những gì hợp pháp thường được thông báo bởi những gì phù hợp về mặt logic với phần còn lại của ngôn ngữ, những gì đơn giản và ngắn gọn và những gì khả thi cho trình biên dịch thực hiện. Lý do có thể có của ủy ban tiêu chuẩn trong việc cân nhắc các yếu tố này là có chủ ý và thú vị - do đó là câu hỏi.


Nó không phải là vấn đề lấy địa chỉ của một nhà xây dựng, nhưng có thể vượt qua loại. Mẫu có thể làm điều đó.
Euphoric

Điều gì xảy ra nếu bạn có một mẫu hàm mà bạn muốn xây dựng một đối tượng bằng cách sử dụng một hàm tạo sẽ được chỉ định làm đối số cho hàm?
Praxeolitic

1
Sẽ có những lựa chọn thay thế cho bất kỳ ví dụ nào tôi có thể nghĩ ra, nhưng vẫn vậy, tại sao các nhà xây dựng nên đặc biệt? Có rất nhiều thứ mà bạn có thể sẽ không sử dụng trong hầu hết các ngôn ngữ lập trình nhưng những trường hợp đặc biệt như thế này thường đi kèm với sự biện minh.
Praxeolitic

1
@RobertHarvey Câu hỏi xảy ra với tôi khi tôi chuẩn bị nhập lớp nhà máy.
Praxeolitic

1
Tôi tự hỏi nếu C ++ 11 std::make_uniquestd::make_sharedcó thể giải quyết thỏa đáng động lực thực tế tiềm ẩn cho câu hỏi này. Đây là các phương thức mẫu, có nghĩa là người ta cần nắm bắt các đối số đầu vào cho hàm tạo, sau đó chuyển tiếp chúng đến hàm tạo thực tế.
rwong

Câu trả lời:


10

Các hàm con trỏ thành viên chỉ có ý nghĩa nếu bạn có nhiều hơn một hàm thành viên có cùng chữ ký - nếu không sẽ chỉ có một giá trị có thể cho con trỏ của bạn. Nhưng điều đó là không thể đối với các bộ điều khiển, vì trong C ++, các hàm tạo khác nhau của cùng một lớp phải có các chữ ký khác nhau.

Cách thay thế cho Stroustrup sẽ là chọn một cú pháp cho C ++ trong đó các hàm tạo có thể có một tên khác với tên lớp - nhưng điều đó sẽ ngăn một số khía cạnh rất tao nhã của cú pháp ctor hiện có và khiến ngôn ngữ trở nên phức tạp hơn. Đối với tôi, nó trông giống như một mức giá cao chỉ để cho phép một tính năng cần thiết hiếm khi có thể được mô phỏng dễ dàng bằng cách "thuê ngoài" việc khởi tạo một đối tượng từ ctor sang một initchức năng khác (một chức năng thành viên bình thường mà con trỏ có thể là thành viên tạo).


2
Tuy nhiên, tại sao lại ngăn chặn memcpy(buffer, (&std::string)(int, char), size)? (Có lẽ là cực kỳ không kosher, nhưng cuối cùng thì đây là C ++.)
Thomas Eding

3
xin lỗi, nhưng những gì bạn viết không có ý nghĩa Tôi thấy không có gì sai khi có con trỏ tới thành viên trỏ đến một hàm tạo. Ngoài ra, nó có vẻ như bạn trích dẫn một cái gì đó, không có liên kết đến nguồn.
BЈовић

1
@ThomasEding: chính xác thì bạn mong đợi câu nói đó sẽ làm gì? Sao chép mã lắp ráp của chuỗi ctor bằng cách nào đó? Làm thế nào "kích thước" sẽ được xác định (ngay cả khi bạn thử một cái gì đó tương đương cho một chức năng thành viên tiêu chuẩn)?
Doc Brown

Tôi hy vọng nó làm điều tương tự như nó sẽ làm như được cung cấp địa chỉ của một con trỏ hàm miễn phí memcpy(buffer, strlen, size). Có lẽ nó sẽ sao chép lắp ráp, nhưng ai biết được. Việc mã có thể được gọi mà không bị sập hay không sẽ đòi hỏi kiến ​​thức về trình biên dịch bạn sử dụng. Cùng xác định kích thước. Nó sẽ phụ thuộc nhiều vào nền tảng, nhưng rất nhiều cấu trúc C ++ không di động được sử dụng trong mã sản xuất. Tôi thấy không có lý do để đặt ra ngoài vòng pháp luật đó.
Thomas Eding

@ThomasEding: Trình biên dịch C ++ phù hợp dự kiến ​​sẽ đưa ra chẩn đoán khi cố gắng truy cập một con trỏ hàm như thể nó là một con trỏ dữ liệu. Một trình biên dịch C ++ không tuân thủ có thể làm bất cứ điều gì, nhưng chúng cũng có thể cung cấp một cách không truy cập vào một hàm tạo. Đó không phải là lý do để thêm một tính năng vào C ++ mà không sử dụng mã tuân thủ.
Bart van Ingen Schenau

5

Hàm tạo là một hàm mà bạn gọi khi đối tượng chưa tồn tại, vì vậy nó không thể là hàm thành viên. Nó có thể là tĩnh.

Một constructor thực sự được gọi bằng một con trỏ này, sau khi bộ nhớ đã được cấp phát nhưng trước khi nó được khởi tạo hoàn toàn. Kết quả là một nhà xây dựng có một số tính năng đặc quyền.

Nếu bạn có một con trỏ tới hàm tạo, nó sẽ phải là một con trỏ tĩnh, một cái gì đó giống như một hàm của nhà máy hoặc một con trỏ đặc biệt cho một cái gì đó sẽ được gọi ngay sau khi cấp phát bộ nhớ. Nó không thể là một hàm thành viên bình thường và vẫn hoạt động như một hàm tạo.

Mục đích hữu ích duy nhất nảy ra trong đầu là một loại con trỏ đặc biệt có thể được chuyển cho toán tử mới để cho phép nó gián tiếp sử dụng hàm tạo nào. Tôi đoán điều đó có thể hữu ích, nhưng nó sẽ đòi hỏi cú pháp mới đáng kể và có lẽ câu trả lời là: họ nghĩ về nó và nó không đáng để nỗ lực.

Nếu bạn chỉ muốn cấu trúc lại mã khởi tạo chung thì một hàm bộ nhớ thông thường thường là một câu trả lời đầy đủ và bạn có thể lấy một con trỏ tới một trong số đó.


Đây có vẻ như là câu trả lời đúng nhất. Tôi nhớ lại một bài viết từ nhiều (nhiều) năm trước liên quan đến nhà điều hành mới và hoạt động nội bộ của nhà điều hành mới. Toán tử new () phân bổ không gian. Toán tử mới gọi hàm tạo với không gian được phân bổ đó. Lấy địa chỉ của một nhà xây dựng là đặc biệt, vì việc gọi nhà xây dựng cần không gian. Quyền truy cập để gọi một nhà xây dựng như thế này là với vị trí mới.
Cửa Bill

1
Từ "tồn tại" che khuất chi tiết rằng một đối tượng có thể có địa chỉ và được cấp phát bộ nhớ nhưng không được khởi tạo. Về chức năng thành viên hay không, tôi nghĩ việc lấy con trỏ này làm cho hàm trở thành hàm thành viên vì nó được liên kết rõ ràng với một thể hiện đối tượng (ngay cả khi chưa được khởi tạo). Điều đó nói rằng, câu trả lời nêu lên một điểm tốt: hàm tạo là hàm thành viên duy nhất có thể được gọi trên một đối tượng chưa được khởi tạo.
Praxeolitic

Nevermind, rõ ràng họ có chỉ định "chức năng thành viên đặc biệt". Khoản 12 của tiêu chuẩn C ++ 11: "Hàm tạo mặc định (12.1), hàm tạo sao chép và toán tử gán sao chép (12.8), hàm tạo di chuyển và toán tử gán chuyển (12.8) và hàm hủy (12.4) là các hàm thành viên đặc biệt ."
Praxeolitic

Và 12.1: "Một hàm tạo sẽ không phải là ảo (10.3) hoặc tĩnh (9.4)." (nhấn mạnh của tôi)
Praxeolitic

1
Thực tế là nếu bạn biên dịch với các biểu tượng gỡ lỗi và tìm kiếm dấu vết ngăn xếp, thực sự có một con trỏ tới hàm tạo. Điều tôi không bao giờ có thể là tìm cú pháp để lấy con trỏ này ( &A::Akhông hoạt động trong bất kỳ trình biên dịch nào tôi đã thử.)
alfC

-3

Điều này là do chúng không phải là kiểu trả về của hàm tạo và bạn không dành bất kỳ khoảng trống nào cho hàm tạo trong bộ nhớ. Giống như bạn làm trong trường hợp biến trong khi khai báo. Ví dụ: nếu u viết biến đơn giản X thì trình biên dịch sẽ tạo ra lỗi vì trình biên dịch sẽ không hiểu ý nghĩa của việc này. Nhưng khi bạn viết Int x; Sau đó, trình biên dịch biết rằng nó biến kiểu dữ liệu int, vì vậy nó sẽ dành một số không gian cho biến.

Kết luận: - vì vậy kết luận là do loại trừ loại trả về, nó sẽ không nhận được địa chỉ trong bộ nhớ.


1
Mã trong hàm tạo phải có một địa chỉ trong bộ nhớ vì nó phải ở đâu đó. Không cần phải dự trữ không gian cho nó trên ngăn xếp nhưng nó phải ở đâu đó trong bộ nhớ. Bạn có thể lấy địa chỉ của các hàm không trả về giá trị. (void)(*fptr)()khai báo một con trỏ tới một hàm không có giá trị trả về.
Praxeolitic

2
Bạn đã bỏ lỡ điểm của câu hỏi - bài đăng gốc hỏi về việc lấy địa chỉ của mã cho hàm tạo, chứ không phải kết quả mà hàm tạo cung cấp. Ngoài ra, trên bảng này, vui lòng sử dụng các từ đầy đủ: "u" không phải là sự thay thế chấp nhận được cho "bạn".
BobDalgleish

Thưa ông, tôi nghĩ rằng nếu chúng ta không đề cập đến bất kỳ loại trả về nào thì trình biên dịch sẽ không đặt một vị trí bộ nhớ cụ thể cho ctor và vị trí đó được đặt bên trong .... Chúng ta có thể lấy địa chỉ của bất kỳ thứ gì trong c ++ không được cung cấp bởi c ++ không trình biên dịch? Nếu sai thì xin vui lòng sửa cho tôi với câu trả lời đúng
Gidel đáng yêu

Và cũng cho tôi biết về biến tham chiếu. Chúng ta có thể lấy địa chỉ của biến tham chiếu không? Nếu không thì địa chỉ printf ("% u", & (& (j))); đang in nếu & j = x trong đó x = 10? Bởi vì địa chỉ được in bởi printf và địa chỉ của x không giống nhau
Gidel đáng yêu

-4

Tôi sẽ đoán mò:

Hàm tạo và hàm hủy của C ++ hoàn toàn không phải là hàm: chúng là các macro. Chúng được đưa vào phạm vi nơi đối tượng được tạo và phạm vi nơi đối tượng bị phá hủy. Đổi lại, không có hàm tạo cũng như hàm hủy, đối tượng chỉ là IS.

Trên thực tế, tôi nghĩ rằng các hàm khác trong lớp cũng không phải là hàm, mà là các hàm nội tuyến KHÔNG được nội tuyến vì bạn lấy địa chỉ của chúng (trình biên dịch nhận ra bạn đang ở trên đó và không nội tuyến hoặc nội tuyến mã vào hàm và tối ưu hóa chức năng đó) và đến lượt chức năng dường như "vẫn ở đó", mặc dù nó sẽ không nếu bạn không lấy địa chỉ của nó.

Bảng ảo của "đối tượng" C ++ không giống như một đối tượng JavaScript, nơi bạn có thể lấy hàm tạo của nó và tạo các đối tượng từ nó trong thời gian chạy qua new XMLHttpRequest.constructor, mà là một tập hợp các con trỏ tới các hàm ẩn danh hoạt động như giao diện với đối tượng này , không bao gồm khả năng tạo đối tượng. Và thậm chí không có ý nghĩa gì khi "xóa" đối tượng, bởi vì nó giống như cố gắng xóa một cấu trúc, bạn không thể: nó chỉ là một nhãn ngăn xếp, chỉ cần viết cho nó khi bạn vui lòng dưới nhãn khác: bạn có thể tự do sử dụng một lớp là 4 số nguyên:

/* i imagine this string gets compiled into a struct, one of which's members happens to be a const char * which is initialized to exactly your string: no function calls are made during construction. */
std::string a = "hello, world";
int *myInt = (int *)(*((void **)&a));
myInt[0] = 3;
myInt[1] = 9;
myInt[2] = 20;
myInt[3] = 300;

Không có rò rỉ bộ nhớ, không có vấn đề gì, ngoại trừ việc bạn lãng phí một cách hiệu quả một loạt không gian ngăn xếp dành riêng cho đối tượng giao thoa và chuỗi, nhưng nó sẽ không phá hủy chương trình của bạn (miễn là bạn không thử sử dụng nó như một chuỗi một lần nữa).

Trên thực tế, nếu các giả định trước đây của tôi là chính xác: chi phí hoàn chỉnh của chuỗi chỉ là chi phí lưu trữ 32 byte này và không gian chuỗi không đổi: các hàm chỉ được sử dụng tại thời điểm biên dịch, và cũng có thể được đưa vào và bỏ đi sau đối tượng được tạo và sử dụng (Như thể bạn đang làm việc với một cấu trúc và chỉ được gọi trực tiếp với nó mà không có bất kỳ lệnh gọi chức năng nào, chắc chắn có các cuộc gọi trùng lặp thay vì nhảy chức năng, nhưng điều này thường nhanh hơn và sử dụng ít không gian hơn). Về bản chất, bất cứ khi nào bạn gọi bất kỳ chức năng nào, trình biên dịch chỉ cần thay thế cuộc gọi đó bằng các hướng dẫn để thực hiện theo nghĩa đen, với các ngoại lệ mà các nhà thiết kế ngôn ngữ đã đặt ra.

Tóm tắt: Các đối tượng C ++ không biết chúng là gì; tất cả các công cụ để giao tiếp với chúng đều được nội tuyến tĩnh và bị mất khi chạy. Điều này làm cho việc làm việc với các lớp hiệu quả như điền vào các cấu trúc dữ liệu và trực tiếp làm việc với dữ liệu đó mà không gọi bất kỳ chức năng nào (các chức năng này được nội tuyến).

Điều này hoàn toàn khác với các cách tiếp cận của COM / ObjectiveC cũng như javascript, giữ lại thông tin kiểu động, với chi phí thời gian chạy, quản lý bộ nhớ, các lệnh gọi, vì trình biên dịch không thể loại bỏ thông tin này: cần thiết cho công văn năng động. Điều này đến lượt chúng ta có khả năng "Nói" với chương trình của chúng ta trong thời gian chạy và phát triển nó trong khi nó đang chạy bằng cách có các thành phần có thể phản xạ.


2
xin lỗi, nhưng một số phần của "câu trả lời" này là sai hoặc nguy hiểm gây hiểu lầm. Đáng buồn thay, không gian bình luận quá nhỏ để liệt kê tất cả (hầu hết các phương thức sẽ không được nội tuyến, điều này sẽ ngăn chặn việc gửi ảo và làm mờ nhị phân; ngay cả khi được nội tuyến, có thể có một bản sao có thể truy cập được ở đâu đó có thể truy cập được, ví dụ mã không liên quan trường hợp xấu nhất làm hỏng ngăn xếp của bạn và trong trường hợp tốt nhất không phù hợp với giả định của bạn; ...)
hoffmale

Câu trả lời là ngây thơ, tôi chỉ muốn bày tỏ dự đoán của mình về lý do tại sao hàm tạo / hàm hủy không thể được tham chiếu. Tôi đồng ý rằng trong trường hợp các lớp ảo, vtable phải tồn tại và mã có thể đánh địa chỉ phải nằm trong bộ nhớ để vtable có thể tham chiếu nó. Tuy nhiên, các lớp không triển khai một lớp ảo, dường như được nội tuyến, như trong trường hợp của std :: string. Không phải mọi thứ đều được nội tuyến, nhưng những thứ dường như không được đưa vào khối mã "ẩn danh" ở đâu đó trong bộ nhớ. Ngoài ra, làm thế nào để mã bị hỏng stack? Chắc chắn chúng tôi đã mất chuỗi, nhưng nếu không, tất cả những gì chúng tôi đã làm là diễn giải lại.
Dmitry

Tham nhũng bộ nhớ xảy ra trong một chương trình máy tính khi nội dung của một vị trí bộ nhớ bị vô tình sửa đổi. Chương trình này thực hiện nó một cách có chủ ý và không cố gắng sử dụng chuỗi đó nữa, vì vậy không có tham nhũng, chỉ lãng phí không gian ngăn xếp. Nhưng vâng, bất biến của chuỗi không còn được duy trì, nó làm lộn xộn phạm vi (cuối cùng, ngăn xếp được phục hồi).
Dmitry

tùy thuộc vào việc triển khai chuỗi mà bạn có thể viết qua byte mà bạn không muốn. Nếu chuỗi là một cái gì đó giống như struct { int size; const char * data; };(dường như bạn giả sử), bạn viết 4 * 4 Byte = 16 byte trên địa chỉ bộ nhớ trong đó bạn chỉ dành 8 byte trên máy x86, do đó 8 byte được ghi trên dữ liệu khác (có thể làm hỏng ngăn xếp của bạn ). May mắn thay, std::stringthông thường có một số tối ưu hóa tại chỗ cho các chuỗi ngắn, vì vậy nó phải đủ lớn cho ví dụ của bạn khi sử dụng một số triển khai tiêu chuẩn chính.
hoffmale

@hoffmale bạn hoàn toàn đúng, nó có thể là 4 byte, nó có thể là 8 hoặc thậm chí là 1 byte. Tuy nhiên, một khi bạn biết kích thước của chuỗi, bạn cũng biết rằng bộ nhớ được xếp chồng lên nhau trong phạm vi hiện tại và bạn có thể sử dụng nó theo ý muốn. Quan điểm của tôi là nếu bạn biết cấu trúc, nó được đóng gói theo cách độc lập với bất kỳ thông tin nào về lớp, không giống như các đối tượng COM có uuid xác định lớp của chúng là một phần của vtable của IUn Unknown. Đổi lại, trình biên dịch truy cập dữ liệu này trực tiếp thông qua nội tuyến hoặc hàm hàm được xử lý.
Dmitry
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.