Nỗ lực của tôi trong việc khởi tạo giá trị được hiểu là một khai báo hàm và tại sao A A (()) không; giải quyết nó?


158

Trong số rất nhiều điều, Stack Overflow đã dạy tôi, đó là cái được gọi là "phân tích phật ý nhất", được thể hiện một cách cổ điển với một dòng như

A a(B()); //declares a function

Trong khi điều này, đối với hầu hết, theo trực giác dường như là khai báo của một đối tượng aloại A, lấy một Bđối tượng tạm thời làm tham số của hàm tạo, thì thực tế nó là một khai báo của một hàm atrả về một A, lấy một con trỏ tới một hàm trả về Bvà chính nó không có tham số nào . Tương tự dòng

A a(); //declares a function

cũng thuộc cùng loại, vì thay vì một đối tượng, nó khai báo một hàm. Bây giờ, trong trường hợp đầu tiên, cách giải quyết thông thường cho vấn đề này là thêm một bộ dấu ngoặc / dấu ngoặc đơn xung quanh B(), vì trình biên dịch sẽ diễn giải nó như là khai báo của một đối tượng

A a((B())); //declares an object

Tuy nhiên, trong trường hợp thứ hai, thực hiện tương tự dẫn đến lỗi biên dịch

A a(()); //compile error

Câu hỏi của tôi là, tại sao? Có, tôi biết rất rõ rằng 'cách giải quyết' chính xác là thay đổi nó thành A a;, nhưng tôi tò mò muốn biết phần bổ sung ()nào cho trình biên dịch trong ví dụ đầu tiên mà sau đó không hoạt động khi áp dụng lại nó trong ví dụ thứ hai. Là A a((B()));cách giải quyết một ngoại lệ cụ thể được viết vào tiêu chuẩn?


20
(B())chỉ là một biểu thức C ++, không có gì hơn. Đó không phải là bất kỳ loại ngoại lệ. Sự khác biệt duy nhất mà nó tạo ra là không có cách nào nó có thể được phân tích thành một loại, và vì vậy nó không.
Pavel Minaev

12
Cũng cần phải lưu ý rằng trường hợp thứ hai, A a();không cùng loại. Đối với trình biên dịch , không bao giờ có cách phân tích khác nhau: Trình khởi tạo tại nơi đó không bao giờ chứa các dấu ngoặc rỗng, vì vậy đây luôn là một khai báo hàm.
Julian Schaub - litb

11
Điểm tuyệt vời của litb là một điểm tinh tế nhưng quan trọng và đáng nhấn mạnh - lý do sự mơ hồ tồn tại trong tuyên bố này 'A a (B ())' nằm trong phân tích cú pháp của 'B ()' -> nó có thể là một biểu thức & một khai báo & trình biên dịch phải 'chọn' từ chối expr - vì vậy nếu B () là một từ chối thì 'a' chỉ có thể là một func Dec (không phải là một biến giảm). Nếu '()' được phép là trình khởi tạo 'A a ()' sẽ không rõ ràng - nhưng không phải là expr vs Dec, nhưng var Dec vs func Dec - không có quy tắc nào thích một từ chối hơn một từ chối khác - và vì vậy '() 'chỉ không được phép làm trình khởi tạo ở đây - và sự mơ hồ không tăng lên.
Faisal Vali

6
A a();không một ví dụ về phân tích cú pháp gây nhiều tranh cãi nhất . Nó chỉ đơn giản là một khai báo hàm, giống như trong C.
Pete Becker

2
"Cách giải quyết" chính xác là thay đổi nó thành A a;"là sai. Điều đó sẽ không cung cấp cho bạn khởi tạo loại POD. Để có được intitialization viết A a{};.
Chúc mừng và hth. - Alf

Câu trả lời:


70

Không có câu trả lời khai sáng, đó chỉ là vì nó không được định nghĩa là cú pháp hợp lệ bởi ngôn ngữ C ++ ... Vì vậy, theo định nghĩa của ngôn ngữ.

Nếu bạn có một biểu thức trong đó thì nó là hợp lệ. Ví dụ:

 ((0));//compiles

Thậm chí đơn giản hơn: bởi vì (x)là một biểu thức C ++ hợp lệ, trong khi ()thì không.

Để tìm hiểu thêm về cách xác định ngôn ngữ và cách trình biên dịch hoạt động, bạn nên tìm hiểu về lý thuyết ngôn ngữ chính thức hoặc cụ thể hơn là Ngữ pháp tự do ngữ cảnh (CFG) và các tài liệu liên quan như máy trạng thái hữu hạn. Nếu bạn quan tâm đến điều đó mặc dù các trang wikipedia sẽ không đủ, bạn sẽ phải lấy một cuốn sách.


45
Thậm chí đơn giản hơn: bởi vì (x)là một biểu thức C ++ hợp lệ, trong khi ()thì không.
Pavel Minaev

Tôi đã chấp nhận câu trả lời này, ngoài ra nhận xét của Pavel cho câu hỏi ban đầu của tôi đã giúp tôi rất nhiều
GRB


29

Khai báo hàm C

Trước hết, có C. Trong C, A a()là khai báo hàm. Ví dụ, putcharcó tuyên bố sau. Thông thường, các khai báo như vậy được lưu trữ trong các tệp tiêu đề, tuy nhiên không có gì ngăn bạn viết chúng theo cách thủ công, nếu bạn biết cách khai báo của hàm trông như thế nào. Tên đối số là tùy chọn trong khai báo, vì vậy tôi đã bỏ qua nó trong ví dụ này.

int putchar(int);

Điều này cho phép bạn viết mã như thế này.

int puts(const char *);
int main() {
    puts("Hello, world!");
}

C cũng cho phép bạn xác định các hàm lấy các hàm làm đối số, với cú pháp dễ đọc trông giống như một lệnh gọi hàm (tốt, nó có thể đọc được, miễn là bạn sẽ không trả về một con trỏ cho hàm).

#include <stdio.h>

int eighty_four() {
    return 84;
}

int output_result(int callback()) {
    printf("Returned: %d\n", callback());
    return 0;
}

int main() {
    return output_result(eighty_four);
}

Như tôi đã đề cập, C cho phép bỏ qua các tên đối số trong các tệp tiêu đề, do đó output_resultsẽ trông như thế này trong tệp tiêu đề.

int output_result(int());

Một đối số trong hàm tạo

Bạn không nhận ra cái đó à? Vâng, hãy để tôi nhắc bạn.

A a(B());

Đúng, đó chính xác là khai báo hàm giống nhau. Aint, aoutput_result, và Bint.

Bạn có thể dễ dàng nhận thấy xung đột của C với các tính năng mới của C ++. Để chính xác, các hàm tạo là tên lớp và dấu ngoặc đơn và thay thế cú pháp khai báo ()thay thế =. Theo thiết kế, C ++ cố gắng tương thích với mã C, và do đó nó phải xử lý trường hợp này - ngay cả khi thực tế không ai quan tâm. Do đó, các tính năng C cũ được ưu tiên hơn các tính năng C ++ mới. Ngữ pháp của khai báo cố gắng khớp tên dưới dạng hàm, trước khi hoàn nguyên cú pháp mới ()nếu thất bại.

Nếu một trong những tính năng đó không tồn tại hoặc có một cú pháp khác (như {}trong C ++ 11), thì vấn đề này sẽ không bao giờ xảy ra đối với cú pháp với một đối số.

Bây giờ bạn có thể hỏi tại sao A a((B()))làm việc. Vâng, chúng ta hãy tuyên bố output_resultvới dấu ngoặc đơn vô dụng.

int output_result((int()));

Nó sẽ không hoạt động. Ngữ pháp yêu cầu biến không nằm trong ngoặc đơn.

<stdin>:1:19: error: expected declaration specifiers or ‘...’ before ‘(’ token

Tuy nhiên, C ++ mong đợi biểu hiện tiêu chuẩn ở đây. Trong C ++, bạn có thể viết đoạn mã sau.

int value = int();

Và mã sau đây.

int value = ((((int()))));

C ++ mong đợi biểu thức bên trong dấu ngoặc đơn là ... cũng ... biểu thức, trái ngược với loại C mong đợi. Dấu ngoặc đơn không có nghĩa gì ở đây. Tuy nhiên, bằng cách chèn các dấu ngoặc đơn vô dụng, khai báo hàm C không khớp và cú pháp mới có thể được khớp đúng (chỉ đơn giản là mong đợi một biểu thức, chẳng hạn như 2 + 2).

Thêm đối số trong hàm tạo

Chắc chắn một đối số là tốt, nhưng hai là gì? Không phải là các nhà xây dựng có thể chỉ có một đối số. Một trong các lớp dựng sẵn có hai đối số làstd::string

std::string hundred_dots(100, '.');

Đây là tất cả tốt và tốt (về mặt kỹ thuật, nó sẽ có hầu hết các phân tích bực tức nếu nó được viết như std::string wat(int(), char()), nhưng hãy trung thực - ai sẽ viết điều đó? Nhưng hãy giả sử mã này có một vấn đề khó chịu. Bạn sẽ cho rằng bạn phải đặt mọi thứ trong ngoặc đơn.

std::string hundred_dots((100, '.'));

Không hoàn toàn như vậy.

<stdin>:2:36: error: invalid conversion from char to const char*’ [-fpermissive]
In file included from /usr/include/c++/4.8/string:53:0,
                 from <stdin>:1:
/usr/include/c++/4.8/bits/basic_string.tcc:212:5: error:   initializing argument 1 of std::basic_string<_CharT, _Traits, _Alloc>::basic_string(const _CharT*, const _Alloc&) [with _CharT = char; _Traits = std::char_traits<char>; _Alloc = std::allocator<char>]’ [-fpermissive]
     basic_string<_CharT, _Traits, _Alloc>::
     ^

Tôi không chắc tại sao g ++ cố gắng chuyển đổi charsang const char *. Dù bằng cách nào, hàm tạo được gọi chỉ với một giá trị kiểu char. Không có tình trạng quá tải có một đối số kiểu char, do đó trình biên dịch bị nhầm lẫn. Bạn có thể hỏi - tại sao đối số là kiểu char?

(100, '.')

Vâng, ,đây là một toán tử dấu phẩy. Toán tử dấu phẩy nhận hai đối số và đưa ra đối số bên phải. Nó không thực sự hữu ích, nhưng đó là điều cần biết để giải thích cho tôi.

Thay vào đó, để giải quyết các phân tích vexing nhất, mã sau đây là cần thiết.

std::string hundred_dots((100), ('.'));

Các đối số là trong ngoặc đơn, không phải toàn bộ biểu thức. Trong thực tế, chỉ cần một trong các biểu thức là trong ngoặc đơn, vì nó đủ để tách khỏi ngữ pháp C một chút để sử dụng tính năng C ++. Mọi thứ đưa chúng ta đến điểm không tranh luận.

Không có đối số trong hàm tạo

Bạn có thể đã nhận thấy eighty_fourchức năng trong lời giải thích của tôi.

int eighty_four();

Vâng, điều này cũng bị ảnh hưởng bởi các phân tích bực tức nhất. Đó là một định nghĩa hợp lệ và rất có thể bạn đã thấy nếu bạn đã tạo các tệp tiêu đề (và bạn nên). Thêm dấu ngoặc đơn không sửa nó.

int eighty_four(());

Tại sao lại như vậy? Vâng, ()không phải là một biểu thức. Trong C ++, bạn phải đặt một biểu thức giữa các dấu ngoặc đơn. Bạn không thể viết auto value = ()bằng C ++, vì ()không có nghĩa gì cả (và ngay cả khi đã làm, như bộ dữ liệu trống (xem Python), đó sẽ là một đối số, không phải bằng không). Thực tế điều đó có nghĩa là bạn không thể sử dụng cú pháp tốc ký mà không sử dụng cú pháp của C ++ 11 {}, vì không có biểu thức nào để đặt trong ngoặc đơn và ngữ pháp C cho khai báo hàm sẽ luôn được áp dụng.


12

Bạn có thể thay thế

A a(());

sử dụng

A a=A();

32
"Cách giải quyết tốt hơn" không tương đương. int a = int();khởi tạo avới 0, int a;achưa được khởi tạo. Một cách giải quyết chính xác là sử dụng A a = {};cho tổng hợp, A a;khi khởi tạo mặc định thực hiện những gì bạn muốn và A a = A();trong tất cả các trường hợp khác - hoặc chỉ sử dụng A a = A();nhất quán. Trong C ++ 11, chỉ cần sử dụngA a {};
Richard Smith

6

Các parens trong cùng trong ví dụ của bạn sẽ là một biểu thức và trong C ++, ngữ pháp định nghĩa expressionmột assignment-expressionhoặc expressionmột dấu phẩy theo sau dấu phẩy và một dấu phẩy khác assignment-expression(Phụ lục A.4 - Tóm tắt ngữ pháp / Biểu thức).

Ngữ pháp tiếp tục định nghĩa một assignment-expressiontrong một số loại biểu thức khác, không có loại nào có thể là không có gì (hoặc chỉ khoảng trắng).

Vì vậy, lý do bạn không thể có A a(())chỉ đơn giản là vì ngữ pháp không cho phép. Tuy nhiên, tôi không thể trả lời tại sao những người tạo ra C ++ không cho phép sử dụng cụ thể trống này như một trường hợp đặc biệt - tôi đoán rằng họ không muốn đưa vào trường hợp đặc biệt như vậy nếu có một sự thay thế hợp lý.

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.