Tại sao tôi cần viết từ khóa 'tự động' một cách rõ ràng?


80

Tôi đang chuyển sang C ++ 11 từ C ++ 98 và đã trở nên quen thuộc với autotừ khóa. Tôi đã tự hỏi tại sao chúng ta cần phải khai báo rõ ràng autonếu trình biên dịch có thể tự động suy ra kiểu. Tôi biết C ++ là một ngôn ngữ được đánh máy mạnh và đây là một quy tắc nhưng liệu có thể đạt được kết quả tương tự mà không khai báo rõ ràng một biến autokhông?


Hãy nhớ rằng họ C phân biệt chữ hoa chữ thường. Hãy gỡ lỗi một số mã JS trong đó tác giả đã bỏ qua "var" và sử dụng biến riêng biệt với các tên như "Bob", "bob" và "boB". Ặc.
PTwr

46
Ngay cả khi có thể, đó sẽ là một ý tưởng vừa phải khủng khiếp. Có thể cho rằng điểm yếu lớn nhất của Python (và các ngôn ngữ tương tự) là thiếu cú ​​pháp khai báo. Đó là một nguồn lỗi chính mà việc gán đơn giản sẽ tạo ra một biến mới.
Konrad Rudolph

@KonradRudolph: JS không tốt hơn với cú pháp khai báo của nó. Tôi nghĩ ý họ muốn nói là không có khả năng hạn chế phạm vi của một biến theo kiểu chi tiết.
user541686,

4
@Mehrdad “thay đổi ngữ nghĩa” ≠ “bắt buộc”. Vấn đề là JavaScript không chấp nhận các khai báo ngầm định. Đúng, chúng khác nhau về mặt ngữ nghĩa nhưng điều đó không giúp ích được chút nào.
Konrad Rudolph

1
Xem thêm câu hỏi kép "tại sao người dùng Perl thực sử dụng từ khóa" của tôi " stackoverflow.com/questions/8023959/why-use-strict-and-warnings/…
Yann TM.

Câu trả lời:


156

Bỏ thông tin rõ ràng autosẽ phá vỡ ngôn ngữ:

ví dụ

int main()
{
    int n;
    {
        auto n = 0; // this shadows the outer n.
    }
}

nơi bạn có thể thấy rằng việc thả rơi autosẽ không làm bóng bên ngoài n.


8
Đã gõ chính xác cùng một thứ. Việc phân biệt gán với khởi tạo sẽ yêu cầu một sự lựa chọn tùy ý trên phần của tiêu chuẩn. Vì chúng tôi đã có quy tắc rằng "bất cứ điều gì có thể là một tuyên bố đều là một tuyên bố", chúng tôi bước vào vùng nước rất âm u.
Người kể chuyện - Unslander Monica

4
Đây không phải là vấn đề. Giống như golang, rõ ràng bạn có thể sử dụng một cái gì đó như n := 0để giới thiệu một biến mới. Tại sao autođược sử dụng là một câu hỏi dựa trên ý kiến.
lllllllll

23
@liliscent - Nó có dựa trên ý kiến ​​không? (a) Nó đã là một từ khóa dành riêng. (b) Ý nghĩa khá rõ ràng. (c) Nó tránh sự cần thiết phải giới thiệu các mã thông báo mới (như :=). (d) Nó đã phù hợp với ngữ pháp. Tôi nghĩ rằng có rất ít chỗ cho ý kiến ​​ở đây.
StoryTeller - Unslander Monica

2
@StoryTeller Bạn không cần mã thông báo mới nếu x = f()khai báo một biến mới (nếu chưa có), nhận kiểu giá trị trả về f`s ... Tuy nhiên, việc yêu cầu tự động khai báo biến một cách rõ ràng sẽ giảm rủi ro khai báo biến mới do tình cờ (ví dụ như vì lỗi đánh máy ...).
Aconcagua

34
@Aconcagua - "khai báo một biến mới (nếu chưa tồn tại)" Nhưng đổ bóng một phần của ngôn ngữ và vẫn phải hoạt động, như Bathsheba minh họa. Đó là một vấn đề lớn hơn người ta tưởng tượng. Nó không phải là về thiết kế ngôn ngữ từ đầu, mà là về việc thay đổi một ngôn ngữ sống. Khó làm hơn nhiều. Kinda như đổi bánh trên một chiếc xe đang tăng tốc.
StoryTeller - Unslander Monica

40

Câu hỏi của bạn cho phép hai cách hiểu:

  • Tại sao chúng ta cần 'tự động'? Chúng ta không thể đơn giản thả nó xuống?
  • Tại sao chúng ta bắt buộc phải sử dụng ô tô? Chúng ta không thể có nó ngầm, nếu nó không được đưa ra?

Bathsheba đã trả lời tuyệt vời cách giải thích đầu tiên, đối với phần thứ hai, hãy xem xét điều sau (giả sử không có tuyên bố nào khác tồn tại cho đến nay; C ++ hợp lệ về mặt giả thuyết ):

int f();
double g();

n = f(); // declares a new variable, type is int;
d = g(); // another new variable, type is double

if(n == d)
{
    n = 7; // reassigns n
    auto d = 2.0; // new d, shadowing the outer one
}

sẽ có thể, ngôn ngữ khác tránh xa khá tốt với (tốt, ngoài các vấn đề shadowing lẽ) ... Nó không phải là như vậy trong C ++, tuy nhiên, và câu hỏi (theo nghĩa của việc giải thích thứ hai) bây giờ là: Tại sao ?

Lần này, câu trả lời không rõ ràng như trong lần giải thích đầu tiên. Tuy nhiên, có một điều hiển nhiên: Yêu cầu rõ ràng đối với từ khóa làm cho ngôn ngữ an toàn hơn (tôi không biết liệu đây có phải là điều đã thúc đẩy ủy ban ngôn ngữ đến quyết định của mình hay không, nhưng nó vẫn là một điểm):

grummel = f();

// ...

if(true)
{
    brummel = f();
  //^ uh, oh, a typo...
}

Chúng ta có thể đồng ý về điều này mà không cần giải thích thêm không?

Tuy nhiên, nguy cơ còn lớn hơn khi không yêu cầu auto là việc thêm một biến toàn cục ở một nơi xa một hàm (ví dụ: trong tệp tiêu đề) có thể biến những gì được dự định trở thành khai báo của local- biến phạm vi trong hàm đó thành một phép gán cho biến toàn cục ... với những hậu quả tai hại tiềm ẩn (và chắc chắn là rất khó hiểu).

(trích dẫn bình luận của psmears do tầm quan trọng của nó - cảm ơn vì đã gợi ý cho)


24
Theo autoquan điểm của tôi, nguy cơ còn lớn hơn khi không yêu cầu là việc thêm một biến toàn cục ở một nơi cách xa một hàm (ví dụ: trong tệp tiêu đề) có thể biến những gì được dự định trở thành khai báo của biến trong hàm đó thành một phép gán cho biến toàn cục ... với những hậu quả tai hại tiềm ẩn (và chắc chắn là rất khó hiểu).
psmears

1
@psmears Các ngôn ngữ như Python tránh điều đó bằng cách yêu cầu đặc tả rõ ràng của một biến là toàn cục / phi địa phương cho các bài tập; theo mặc định, nó chỉ tạo một biến cục bộ mới với tên đó. (Tất nhiên, bạn có thể đọc từ một biến toàn cục mà không cần global <variable>câu lệnh.) Điều đó sẽ yêu cầu sửa đổi nhiều hơn đối với ngôn ngữ C ++, tất nhiên, vì vậy có lẽ sẽ không khả thi.
JAB

@JAB - vâng, tôi biết điều đó ... Tôi không đề cập đến nó bởi vì, như bạn nói, nó sẽ yêu cầu sửa đổi nhiều hơn đối với ngôn ngữ :)
psmears

FWIW, điều đã thúc đẩy ủy ban ngôn ngữ rất có thể là lịch sử. AFAIK, khi C được viết ban đầu, các biến cục bộ được lưu trên ngăn xếp và C yêu cầu tất cả các biến phải được khai báo rõ ràng trước tiên trong một khối. Điều đó cho phép trình biên dịch xác định yêu cầu lưu trữ cho khối đó trước khi biên dịch phần còn lại của mã và cho phép nó đưa ra chuỗi lệnh chính xác để cấp phát không gian trên ngăn xếp. IIRC MOV R6 R5 SUB #nnn R6trên PDP-11 giả sử R5 được sử dụng làm con trỏ khung và R6 là con trỏ ngăn xếp. nnn là số byte dung lượng cần thiết.
dgnuff

2
Mọi người quản lý để sử dụng Python, điều này vui vẻ khai báo một biến mỗi khi một tên mới xuất hiện ở bên trái của nhiệm vụ (ngay cả khi tên đó là lỗi đánh máy). Nhưng tôi coi đó là một trong những sai sót nghiêm trọng hơn của ngôn ngữ.
hobbs

15

không thể đạt được kết quả tương tự mà không khai báo rõ ràng một biến auto?

Tôi sẽ diễn đạt lại câu hỏi của bạn một chút theo cách sẽ giúp bạn hiểu tại sao bạn cần auto:

Không thể đạt được kết quả tương tự mà không sử dụng trình giữ chỗ kiểu rõ ràng ?

Nó không thể thực hiện được ? Tất nhiên là "có thể". Câu hỏi là liệu nó có xứng đáng với nỗ lực để làm điều đó hay không.

Hầu hết các cú pháp trong các ngôn ngữ khác không có tên kiểu đều hoạt động theo một trong hai cách. Có một cách giống như Go, nơi name := value;khai báo một biến. Và có một cách giống như Python, nơi name = value;khai báo một biến mới nếu nametrước đó chưa được khai báo.

Giả sử rằng không có vấn đề cú pháp nào khi áp dụng một trong hai cú pháp cho C ++ (mặc dù tôi có thể thấy rằng identifiertheo sau :trong C ++ có nghĩa là "tạo nhãn"). Vì vậy, bạn mất gì so với trình giữ chỗ?

Chà, tôi không thể làm điều này nữa:

auto &name = get<0>(some_tuple);

Hãy xem, autoluôn luôn có nghĩa là "giá trị". Nếu bạn muốn lấy một tham chiếu, bạn cần phải sử dụng một cách rõ ràng &. Và nó sẽ không biên dịch được nếu biểu thức gán là một prvalue. Cả cú pháp dựa trên phép gán đều không có cách nào để phân biệt giữa các tham chiếu và giá trị.

Bây giờ, bạn có thể thực hiện các cú pháp gán như vậy để suy ra các tham chiếu nếu giá trị đã cho là một tham chiếu. Nhưng điều đó có nghĩa là bạn không thể làm:

auto name = get<0>(some_tuple);

Điều này sao chép từ bộ, tạo ra một đối tượng độc lập với some_tuple. Đôi khi, đó chính xác là những gì bạn muốn. Điều này thậm chí còn hữu ích hơn nếu bạn muốn chuyển từ tuple với auto name = get<0>(std::move(some_tuple));.

OK, vì vậy có lẽ chúng ta có thể mở rộng các cú pháp này một chút để giải thích sự khác biệt này. Có thể &name := value;hoặc &name = value;có nghĩa là để suy ra một tài liệu tham khảo như thế nào auto&.

Được rồi. Cái này thì sao:

decltype(auto) name = some_thing();

Ờ, đúng vậy; C ++ thực sự có hai trình giữ chỗ: autodecltype(auto) . Ý tưởng cơ bản của việc khấu trừ này là nó hoạt động chính xác như thể bạn đã làm decltype(expr) name = expr;. Vì vậy, trong trường hợp của chúng ta, nếu some_thing()là một đối tượng, nó sẽ suy ra một đối tượng. Nếu some_thing()là một tham chiếu, nó sẽ suy ra một tham chiếu.

Điều này rất hữu ích khi bạn đang làm việc trong mã mẫu và không chắc chắn chính xác giá trị trả về của một hàm sẽ là gì. Điều này rất tốt cho việc chuyển tiếp và nó là một công cụ thiết yếu, ngay cả khi nó không được sử dụng rộng rãi.

Vì vậy, bây giờ chúng ta cần thêm nhiều hơn vào cú pháp của mình. name ::= value;có nghĩa là "làm những gì decltype(auto)hiện". Tôi không có phiên bản tương đương cho biến thể Pythonic.

Nhìn vào cú pháp này, không phải là bạn dễ vô tình nhập sai? Không chỉ vậy, nó hầu như không tự ghi lại. Ngay cả khi bạn chưa từng thấy decltype(auto)trước đây, nó đủ lớn và rõ ràng để bạn ít nhất có thể dễ dàng nhận ra rằng có điều gì đó đặc biệt đang diễn ra. Trong khi sự khác biệt trực quan giữa ::=:=là tối thiểu.

Nhưng đó là ý kiến; có nhiều vấn đề thực chất hơn. Hãy xem, tất cả những điều này đều dựa trên việc sử dụng cú pháp gán. Chà ... còn những chỗ bạn không thể sử dụng cú pháp gán thì sao? Như thế này:

for(auto &x : container)

Chúng ta có thay đổi điều đó thành for(&x := container)không? Bởi vì điều đó dường như đang nói điều gì đó rất khác so với dựa trên phạm vi for. Có vẻ như đó là câu lệnh khởi tạo từ một forvòng lặp thông thường , không phải dựa trên phạm vi for. Nó cũng sẽ là một cú pháp khác với các trường hợp không được suy luận.

Ngoài ra, việc sao chép-khởi tạo (sử dụng =) không giống trong C ++ với khởi tạo trực tiếp (sử dụng cú pháp hàm tạo). Vì vậy, name := value;có thể không hoạt động trong trường hợp auto name(value)sẽ có.

Chắc chắn, bạn có thể khai báo rằng :=sẽ sử dụng khởi tạo trực tiếp, nhưng điều đó sẽ khá tương đồng với cách phần còn lại của C ++ hoạt động.

Ngoài ra, còn một thứ nữa: C ++ 14. Nó cung cấp cho chúng tôi một tính năng khấu trừ hữu ích: khấu trừ loại trả lại. Nhưng điều này dựa trên trình giữ chỗ. Cũng giống như dựa trên phạm vi for, về cơ bản nó dựa trên một tên kiểu được trình biên dịch điền vào, không phải bởi một số cú pháp áp dụng cho một tên và biểu thức cụ thể.

Hãy xem, tất cả những vấn đề này đều xuất phát từ cùng một nguồn: bạn đang phát minh ra cú pháp hoàn toàn mới để khai báo các biến. Khai báo dựa trên trình giữ chỗ không cần phải phát minh ra cú pháp mới . Họ đang sử dụng cùng một cú pháp như trước đây; họ chỉ đang sử dụng một từ khóa mới hoạt động giống như một loại, nhưng có một ý nghĩa đặc biệt. Đây là những gì cho phép nó hoạt động dựa trên phạm vi forvà loại trừ kiểu trả về. Nó là thứ cho phép nó có nhiều dạng ( autoso với decltype(auto)). Và kể từ đó trở đi.

Trình giữ chỗ hoạt động vì chúng là giải pháp đơn giản nhất cho vấn đề, đồng thời giữ lại tất cả các lợi ích và tính tổng quát của việc sử dụng tên kiểu thực tế. Nếu bạn nghĩ ra một giải pháp thay thế khác hoạt động phổ biến như trình giữ chỗ, thì rất khó có khả năng nó sẽ đơn giản như trình giữ chỗ.

Trừ khi nó chỉ là đánh vần các trình giữ chỗ với các từ khóa hoặc ký hiệu khác nhau ...


2
IMHO, đây là câu trả lời duy nhất giải quyết một số lý do cơ bản đằng sau việc lựa chọn trình giữ chỗ. Loại trừ trong lambda chung có thể là một ví dụ khác. Thật tiếc khi câu trả lời này nhận được rất ít lượt ủng hộ chỉ vì nó được đăng hơi muộn ...
llllllllll

@liliscent: " Loại trừ trong lambda chung có thể là một ví dụ khác. " Tôi không đề cập đến điều đó vì nó có ngữ nghĩa khác với autokhai báo / khấu trừ giá trị trả về.
Nicol Bolas

@liliscent: Thật vậy, câu trả lời này là muộn cho bữa tiệc. Đã tăng. (Một cải tiến mặc dù sẽ là một đề cập đến C ++ ý kiến cho rằng nếu một cái gì đó có thể là một lời tuyên bố sau đó nó một bản tuyên bố.)
Bathsheba

12

Tóm lại: autocó thể bị loại bỏ trong một số trường hợp, nhưng điều đó sẽ dẫn đến sự không nhất quán.

Trước hết, như được trỏ, cú pháp khai báo trong C ++ là <type> <varname>. Khai báo rõ ràng yêu cầu một số loại hoặc ít nhất một từ khóa khai báo ở vị trí của nó. Vì vậy, chúng tôi có thể sử dụng var <varname>hoặc declare <varname>hoặc cái gì đó, nhưng autolà một từ khóa lâu đời trong C ++ và là một ứng cử viên tốt cho từ khóa loại trừ tự động.

Có thể khai báo ngầm các biến bằng phép gán mà không phá vỡ mọi thứ không?

Thỉnh thoảng đúng. Bạn không thể thực hiện gán bên ngoài các hàm, vì vậy bạn có thể sử dụng cú pháp gán cho các khai báo ở đó. Nhưng cách tiếp cận như vậy sẽ mang lại sự mâu thuẫn trong ngôn ngữ, có thể dẫn đến sai sót của con người.

a = 0; // Error. Could be parsed as auto declaration instead.
int main() {
  return 0;
}

Và khi nói đến bất kỳ loại khai báo rõ ràng nào của biến cục bộ là chúng là cách để kiểm soát phạm vi của một biến.

a = 1; // use a variable declared before or outside
auto b = 2; // declare a variable here

Nếu cú ​​pháp không rõ ràng được cho phép, việc khai báo các biến toàn cục có thể đột ngột chuyển đổi các khai báo ngầm định cục bộ thành các phép gán. Việc tìm kiếm những chuyển đổi đó sẽ yêu cầu kiểm tra mọi thứ . Và để tránh va chạm, bạn sẽ cần những cái tên duy nhất cho tất cả các khối cầu, loại này sẽ phá hủy toàn bộ ý tưởng về phạm vi. Vì vậy, nó thực sự tồi tệ.


11

autolà một từ khóa mà bạn có thể sử dụng ở những nơi mà bạn thường cần chỉ định một loại .

  int x = some_function();

Có thể được thực hiện chung chung hơn bằng cách làm cho intkiểu tự động được suy ra:

  auto x = some_function();

Vì vậy, nó là một phần mở rộng bảo thủ cho ngôn ngữ; nó phù hợp với cú pháp hiện có. Không có nó x = some_function()sẽ trở thành một câu lệnh gán, không còn là một khai báo.


9

cú pháp phải rõ ràng và cũng tương thích ngược.

Nếu auto bị loại bỏ sẽ không có cách nào để phân biệt giữa các câu lệnh và định nghĩa.

auto n = 0; // fine
n=0; // statememt, n is undefined.

3
Điểm quan trọng là nó autođã là một từ khóa (nhưng với ý nghĩa lỗi thời), vì vậy nó không phá vỡ mã khi sử dụng nó làm tên. Đó là lý do trong khi một từ khóa tốt hơn, như varhoặc let, không được chọn thay thế.
Frax

1
@Frax IMO autothực sự là một từ khóa khá tuyệt vời cho điều này: nó diễn đạt chính xác những gì nó viết tắt, cụ thể là nó thay thế tên kiểu bằng “kiểu tự động”. Với từ khóa như varhoặc let, do đó bạn nên yêu cầu từ khóa đó ngay cả khi loại được chỉ định rõ ràng, tức là var int n = 0hoặc tương tự var n:Int = 0. Về cơ bản đây là cách nó được thực hiện trong Rust.
bùng binh

1
@leftaroundabout Mặc dù autochắc chắn là tuyệt vời trong bối cảnh cú pháp hiện có, nhưng tôi có thể nói rằng một cái gì đó giống như var int x = 42định nghĩa biến cơ bản, với var x = 42int x = 42dưới dạng các viết tắt, sẽ có ý nghĩa hơn cú pháp hiện tại nếu được coi là ngoài nội dung lịch sử. Nhưng chủ yếu là vấn đề về hương vị. Nhưng, bạn nói đúng, tôi nên viết "một trong những lý do" thay vì "một lý do" trong nhận xét ban đầu của tôi :)
Frax

@leftaroundabout: "auto thực sự là một từ khóa khá tuyệt vời cho điều này: nó diễn đạt chính xác những gì nó viết tắt, cụ thể là nó thay thế tên loại bằng 'loại tự động'" Điều đó không đúng. Không có "loại tự động".
Các cuộc đua ánh sáng trong quỹ đạo

@LightnessRacesinOrbit trong bất kỳ ngữ cảnh nhất định nào mà bạn có thể sử dụng auto, có một kiểu tự động (một kiểu khác, tùy thuộc vào biểu thức).
bùng binh bên trái

3

Thêm vào các câu trả lời trước, một lưu ý bổ sung từ một cái rắm cũ: Có vẻ như bạn có thể thấy đó là một lợi thế khi có thể bắt đầu sử dụng một biến mới mà không cần khai báo nó theo bất kỳ cách nào.

Trong các ngôn ngữ có khả năng định nghĩa ngầm các biến, đây có thể là một vấn đề lớn, đặc biệt là trong các hệ thống lớn hơn. Bạn mắc một lỗi đánh máy và bạn gỡ lỗi trong nhiều giờ chỉ để phát hiện ra rằng bạn đã vô tình đưa vào một biến có giá trị bằng 0 (hoặc tệ hơn) - bluevs bleu, labelvs lable... kết quả là bạn không thể thực sự tin tưởng bất kỳ mã nào nếu không kiểm tra kỹ lưỡng chính xác tên biến.

Việc chỉ sử dụng autocho cả trình biên dịch và người bảo trì biết rằng bạn có ý định khai báo một biến mới.

Hãy nghĩ về điều đó, để có thể tránh được những cơn ác mộng này, câu lệnh 'ngầm định không có' đã được giới thiệu trong FORTRAN - và bạn thấy nó được sử dụng trong tất cả các chương trình FORTRAN nghiêm túc ngày nay. Không có nó chỉ đơn giản là ... đáng sợ.

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.