Tại sao nên sử dụng static_cast <int> (x) thay vì (int) x?


667

Tôi đã nghe nói rằng static_castchức năng này nên được ưu tiên hơn so với kiểu đúc kiểu C hoặc kiểu hàm đơn giản. Điều này có đúng không? Tại sao?


36
Phản đối danh dự của bạn, hỏi và trả lời .
Graeme Perrow

25
Tôi không đồng ý, câu hỏi khác này là về việc mô tả sự khác biệt giữa các diễn viên giới thiệu trong C ++. Câu hỏi này là về tính hữu dụng thực sự của static_cast, hơi khác một chút.
Vincent Robert

2
Chúng tôi chắc chắn có thể hợp nhất hai câu hỏi, nhưng điều chúng tôi cần giữ lại từ luồng này là lợi thế của việc sử dụng các chức năng so với truyền kiểu C, hiện chỉ được đề cập trong câu trả lời một dòng trong luồng khác, không có phiếu bầu .
Tommy Herbert

6
Câu hỏi này là về các loại "tích hợp", như int, trong khi câu hỏi đó là về các loại lớp. Đó dường như là một sự khác biệt đủ đáng kể để xứng đáng với một lời giải thích riêng biệt.

9
static_cast thực sự là một toán tử, không phải là một hàm.
ThomasMcLeod

Câu trả lời:


633

Nguyên nhân chủ yếu là phôi C cổ điển làm cho không có sự phân biệt giữa những gì chúng ta gọi là static_cast<>(), reinterpret_cast<>(), const_cast<>(), và dynamic_cast<>(). Bốn điều này hoàn toàn khác nhau.

A static_cast<>()thường an toàn. Có một chuyển đổi hợp lệ trong ngôn ngữ hoặc một nhà xây dựng thích hợp làm cho nó có thể. Lần duy nhất có một chút rủi ro là khi bạn bỏ xuống một lớp kế thừa; bạn phải chắc chắn rằng đối tượng thực sự là hậu duệ mà bạn cho là nó, có nghĩa là bên ngoài ngôn ngữ (giống như một lá cờ trong đối tượng). A dynamic_cast<>()là an toàn miễn là kết quả được kiểm tra (con trỏ) hoặc một ngoại lệ có thể được đưa vào tài khoản (tham chiếu).

Mặt khác reinterpret_cast<>()(hoặc a const_cast<>()) luôn nguy hiểm. Bạn nói với trình biên dịch: "hãy tin tôi: tôi biết điều này không giống như một foo(điều này trông như thể nó không thể thay đổi), nhưng nó là".

Vấn đề đầu tiên là hầu như không thể biết được cái nào sẽ xảy ra trong dàn diễn viên kiểu C mà không nhìn vào các đoạn mã lớn và phân tán và biết tất cả các quy tắc.

Hãy giả sử như sau:

class CDerivedClass : public CMyBase {...};
class CMyOtherStuff {...} ;

CMyBase  *pSomething; // filled somewhere

Bây giờ, hai cái này được biên dịch theo cùng một cách:

CDerivedClass *pMyObject;
pMyObject = static_cast<CDerivedClass*>(pSomething); // Safe; as long as we checked

pMyObject = (CDerivedClass*)(pSomething); // Same as static_cast<>
                                     // Safe; as long as we checked
                                     // but harder to read

Tuy nhiên, hãy xem mã gần như giống hệt nhau này:

CMyOtherStuff *pOther;
pOther = static_cast<CMyOtherStuff*>(pSomething); // Compiler error: Can't convert

pOther = (CMyOtherStuff*)(pSomething);            // No compiler error.
                                                  // Same as reinterpret_cast<>
                                                  // and it's wrong!!!

Như bạn có thể thấy, không có cách nào dễ dàng để phân biệt giữa hai tình huống mà không biết nhiều về tất cả các lớp liên quan.

Vấn đề thứ hai là dàn diễn viên kiểu C quá khó định vị. Trong các biểu thức phức tạp, có thể rất khó để nhìn thấy các phôi kiểu C. Hầu như không thể viết một công cụ tự động cần định vị các phôi kiểu C (ví dụ như một công cụ tìm kiếm) mà không có giao diện trình biên dịch C ++ đầy đủ. Mặt khác, thật dễ dàng để tìm kiếm "static_cast <" hoặc "reinterpret_cast <".

pOther = reinterpret_cast<CMyOtherStuff*>(pSomething);
      // No compiler error.
      // but the presence of a reinterpret_cast<> is 
      // like a Siren with Red Flashing Lights in your code.
      // The mere typing of it should cause you to feel VERY uncomfortable.

Điều đó có nghĩa là, không chỉ các diễn viên kiểu C nguy hiểm hơn, mà việc tìm ra tất cả chúng để đảm bảo rằng chúng đúng là rất khó khăn.


30
Bạn không nên sử dụng static_castđể bỏ xuống một hệ thống phân cấp thừa kế, nhưng đúng hơn dynamic_cast. Điều đó sẽ trả về con trỏ null hoặc con trỏ hợp lệ.
David Thornley

41
@David Thornley: Tôi đồng ý, thường. Tôi nghĩ rằng tôi đã chỉ ra những cảnh báo để sử dụng static_casttrong tình huống đó. dynamic_castcó thể an toàn hơn, nhưng nó không phải luôn luôn là lựa chọn tốt nhất. Đôi khi bạn biết rằng một con trỏ trỏ đến một kiểu con nhất định, có nghĩa là mờ đối với trình biên dịch và a static_castnhanh hơn. Trong ít nhất một số môi trường, dynamic_castyêu cầu hỗ trợ trình biên dịch tùy chọn và chi phí thời gian chạy (bật RTTI) và bạn có thể không muốn bật nó chỉ cho một vài kiểm tra mà bạn có thể tự thực hiện. RTTI của C ++ chỉ là một giải pháp khả thi cho vấn đề.
Euro Micelli

18
Yêu cầu của bạn về phôi C là sai. Tất cả các phôi C là chuyển đổi giá trị, gần như tương đương với C ++ static_cast. Tương đương C reinterpret_cast*(destination_type *)&, tức là lấy địa chỉ của đối tượng, chuyển địa chỉ đó sang một con trỏ đến một loại khác, sau đó hủy bỏ hội nghị. Ngoại trừ trường hợp các kiểu ký tự hoặc các kiểu cấu trúc nhất định mà C xác định hành vi của cấu trúc này, nó thường dẫn đến hành vi không xác định trong C.
R .. GitHub DỪNG GIÚP ICE

11
Câu trả lời tốt của bạn giải quyết cơ thể của bài viết. Tôi đang tìm kiếm một câu trả lời cho tiêu đề "tại sao sử dụng static_cast <int> (x) thay vì (int) x". Đó là, đối với loại int(và intmột mình), tại sao sử dụng static_cast<int>so với (int)lợi ích duy nhất dường như là với các biến lớp và con trỏ. Yêu cầu bạn giải thích về điều này.
chux - Phục hồi Monica

31
@chux, int dynamic_castkhông áp dụng, nhưng tất cả các lý do khác đều có. Ví dụ: nói let của vlà một tham số chức năng khai báo là float, sau đó (int)vstatic_cast<int>(v). Nhưng nếu bạn thay đổi tham số thành float*, (int)vlặng lẽ trở thành reinterpret_cast<int>(v)trong khi static_cast<int>(v)bất hợp pháp và được trình biên dịch bắt chính xác.
Euro Micelli

114

Một mẹo thực dụng: bạn có thể dễ dàng tìm kiếm từ khóa static_cast trong mã nguồn của mình nếu bạn có kế hoạch dọn dẹp dự án.


3
bạn cũng có thể tìm kiếm bằng dấu ngoặc như "(int)" nhưng câu trả lời tốt và lý do hợp lệ để sử dụng kiểu truyền C ++.
Mike

4
@Mike sẽ tìm thấy dương tính giả - một khai báo hàm với một inttham số duy nhất .
Nathan Osman

1
Điều này có thể đưa ra những phủ định sai: nếu bạn đang tìm kiếm một cơ sở mã mà bạn không phải là tác giả duy nhất, bạn sẽ không tìm thấy các diễn viên kiểu C mà những người khác có thể đã giới thiệu vì một số lý do.
Ruslan

7
Làm thế nào để làm điều này sẽ giúp dọn dẹp dự án?
Bilow

Bạn sẽ không tìm kiếm static_cast, vì rất có thể đó là cái đúng. Bạn muốn lọc static_cast, trong khi bạn tìm kiếm reinterpret_cast, const_cast và thậm chí có thể động_cast, vì những nơi đó sẽ chỉ ra những địa điểm có thể được thiết kế lại. C-cast trộn tất cả lại với nhau và không cho bạn lý do để casting.
Dragan

78

Tóm lại :

  1. static_cast<>() cung cấp cho bạn khả năng kiểm tra thời gian biên dịch, dàn diễn viên C-Style thì không.
  2. static_cast<>()có thể được phát hiện dễ dàng ở bất cứ đâu trong mã nguồn C ++; ngược lại, diễn viên C_Style khó phát hiện hơn.
  3. Ý định được truyền đạt tốt hơn nhiều bằng cách sử dụng phôi C ++.

Giải thích thêm :

Các diễn viên tĩnh thực hiện chuyển đổi giữa các loại tương thích . Nó tương tự như dàn diễn viên theo phong cách C, nhưng hạn chế hơn. Ví dụ, ép kiểu C sẽ cho phép một con trỏ nguyên trỏ đến một char.

char c = 10;       // 1 byte
int *p = (int*)&c; // 4 bytes

Vì điều này dẫn đến một con trỏ 4 byte trỏ đến 1 byte bộ nhớ được phân bổ, việc ghi vào con trỏ này sẽ gây ra lỗi thời gian chạy hoặc sẽ ghi đè lên một số bộ nhớ liền kề.

*p = 5; // run-time error: stack corruption

Ngược lại với kiểu đúc C, ép kiểu tĩnh sẽ cho phép trình biên dịch kiểm tra xem các kiểu dữ liệu con trỏ và con trỏ có tương thích hay không, cho phép lập trình viên bắt được phép gán con trỏ không chính xác này trong quá trình biên dịch.

int *q = static_cast<int*>(&c); // compile-time error

Đọc thêm về:
Sự khác biệt giữa truyền kiểu static_cast <> và kiểu C

Truyền thông thường so với static_cast so với Dynamic_cast


21
Tôi không đồng ý rằng static_cast<>()dễ đọc hơn. Ý tôi là, đôi khi nó là như vậy, nhưng hầu hết thời gian - đặc biệt là về các loại số nguyên cơ bản - nó chỉ dài dòng khủng khiếp và không cần thiết. Ví dụ: Đây là một hàm hoán đổi các byte của một từ 32 bit. Nó gần như không thể đọc được bằng cách sử dụng static_cast<uint##>()phôi, nhưng khá dễ hiểu khi sử dụng (uint##)phôi. Ảnh mã: imgur.com/NoHbGve
Todd Lehman

3
@ToddLehman: Cảm ơn bạn, nhưng tôi cũng không nói always. (nhưng hầu hết các lần có) Chắc chắn có những trường hợp diễn viên kiểu c dễ đọc hơn. Đó là một trong những lý do khiến phong cách c vẫn còn sống và đá trong c ++ imho. :) Nhân tiện, đó là một ví dụ rất hay
Rika

8
Mã @ToddLehman trong hình ảnh đó sử dụng hai phôi được xâu chuỗi ( (uint32_t)(uint8_t)) để đạt được các byte đó ngoài mức thấp nhất được đặt lại. Cho rằng có bitwise và ( 0xFF &). Sử dụng phôi là che giấu ý định.
Tiö Tiib

28

Câu hỏi lớn hơn so với việc chỉ sử dụng đúc kiểu héo static_cast hoặc C vì có những điều khác nhau xảy ra khi sử dụng phôi kiểu C. Các toán tử đúc C ++ được dự định để làm cho các hoạt động này rõ ràng hơn.

Trên bề mặt phôi kiểu static_cast và C xuất hiện cùng một thứ, ví dụ như khi truyền một giá trị này sang giá trị khác:

int i;
double d = (double)i;                  //C-style cast
double d2 = static_cast<double>( i );  //C++ cast

Cả hai đều đúc giá trị số nguyên thành gấp đôi. Tuy nhiên khi làm việc với con trỏ mọi thứ trở nên phức tạp hơn. vài ví dụ:

class A {};
class B : public A {};

A* a = new B;
B* b = (B*)a;                                  //(1) what is this supposed to do?

char* c = (char*)new int( 5 );                 //(2) that weird?
char* c1 = static_cast<char*>( new int( 5 ) ); //(3) compile time error

Trong ví dụ này (1) có thể OK vì đối tượng được chỉ đến bởi A thực sự là một thể hiện của B. Nhưng nếu bạn không biết tại thời điểm đó trong mã thì điều gì thực sự chỉ đến? (2) có thể hoàn toàn hợp pháp (bạn chỉ muốn xem xét một byte của số nguyên), nhưng cũng có thể là một lỗi trong trường hợp một lỗi sẽ tốt, như (3). Các toán tử đúc C ++ được dự định để phơi bày các vấn đề này trong mã bằng cách cung cấp các lỗi thời gian biên dịch hoặc thời gian chạy khi có thể.

Vì vậy, để "đúc giá trị" nghiêm ngặt, bạn có thể sử dụng static_cast. Nếu bạn muốn truyền đa hình thời gian chạy của con trỏ, hãy sử dụng Dynamic_cast. Nếu bạn thực sự muốn quên về các loại, bạn có thể sử dụng reintinteret_cast. Và để ném const ra khỏi cửa sổ, có const_cast.

Họ chỉ làm cho mã rõ ràng hơn để có vẻ như bạn biết những gì bạn đang làm.


26

static_castcó nghĩa là bạn không thể vô tình const_casthoặc reinterpret_cast, đó là một điều tốt.


4
Các ưu điểm bổ sung (dù khá nhỏ) so với dàn diễn viên theo phong cách C là nó nổi bật hơn (làm điều gì đó có khả năng xấu nên trông xấu xí) và nó có khả năng grep hơn.
Michael Burr

4
grep-ability luôn là một điểm cộng, trong cuốn sách của tôi.
Branan

7
  1. Cho phép các phôi được tìm thấy dễ dàng trong mã của bạn bằng cách sử dụng grep hoặc các công cụ tương tự.
  2. Làm cho nó rõ ràng loại diễn viên bạn đang làm và thu hút sự giúp đỡ của nhà soạn nhạc trong việc thực thi nó. Nếu bạn chỉ muốn bỏ đi const-ness, thì bạn có thể sử dụng const_cast, điều này sẽ không cho phép bạn thực hiện các loại chuyển đổi khác.
  3. Các diễn viên vốn đã xấu xí - bạn với tư cách là một lập trình viên đang ghi đè cách trình biên dịch thường xử lý mã của bạn. Bạn đang nói với trình biên dịch, "Tôi biết rõ hơn bạn." Trong trường hợp đó, điều hợp lý là việc thực hiện dàn diễn viên phải là một điều đau đớn vừa phải và họ nên bám vào mã của bạn, vì chúng có thể là một vấn đề.

Xem Giới thiệu C ++ hiệu quả


Tôi hoàn toàn đồng ý với điều này cho các lớp nhưng việc sử dụng kiểu C ++ cho các kiểu POD có ý nghĩa gì không?
Zachary Kraus

Tôi nghĩ vậy. Tất cả 3 lý do áp dụng cho POD và thật hữu ích khi chỉ có một quy tắc, thay vì quy tắc riêng cho các lớp và POD.
JohnMcG

Thật thú vị, tôi có thể phải sửa đổi cách tôi thực hiện các diễn viên của mình trong mã tương lai cho các loại POD.
Zachary Kraus

7

Đó là về mức độ an toàn mà bạn muốn áp đặt.

Khi bạn viết (bar) foo(tương đương với reinterpret_cast<bar> foonếu bạn chưa cung cấp toán tử chuyển đổi loại), bạn đang nói với trình biên dịch bỏ qua loại an toàn và chỉ cần làm như đã nói.

Khi bạn viết, static_cast<bar> foobạn yêu cầu trình biên dịch ít nhất kiểm tra xem chuyển đổi loại có ý nghĩa hay không, và đối với các loại tích phân, để chèn một số mã chuyển đổi.


EDIT 2014 / 02-26

Tôi đã viết câu trả lời này hơn 5 năm trước và tôi đã hiểu sai. (Xem bình luận.) Nhưng nó vẫn được nâng cấp!


8
(bar) foo không tương đương với reinterpret_cast <bar> (foo). Các quy tắc cho "(TYPE) expr" là nó sẽ chọn kiểu truyền C ++ phù hợp để sử dụng, có thể bao gồm reinterpret_cast.
Richard Corden

Điểm tốt. Euro Micelli đã đưa ra câu trả lời dứt khoát cho câu hỏi này.
Pitarou

1
Ngoài ra, nó là static_cast<bar>(foo), với dấu ngoặc đơn. Tương tự cho reinterpret_cast<bar>(foo).
LF

6

Diễn viên C Style rất dễ bỏ lỡ trong một khối mã. Diễn viên phong cách C ++ không chỉ thực hành tốt hơn; họ cung cấp một mức độ linh hoạt lớn hơn nhiều.

reinterpret_cast cho phép tích hợp chuyển đổi loại con trỏ, tuy nhiên có thể không an toàn nếu sử dụng sai.

static_cast cung cấp chuyển đổi tốt cho các loại số, ví dụ như từ enums sang ints hoặc ints sang float hoặc bất kỳ loại dữ liệu nào bạn tự tin về loại. Nó không thực hiện bất kỳ kiểm tra thời gian chạy.

mặt khác, Dynamic_cast sẽ thực hiện các kiểm tra này gắn cờ mọi chuyển nhượng hoặc chuyển đổi mơ hồ. Nó chỉ hoạt động trên con trỏ và tài liệu tham khảo và phát sinh chi phí.

Có một vài người khác nhưng đây là những người chính bạn sẽ đi qua.


4

static_cast, ngoài việc thao tác con trỏ đến các lớp, cũng có thể được sử dụng để thực hiện chuyển đổi được xác định rõ ràng trong các lớp, cũng như để thực hiện chuyển đổi tiêu chuẩn giữa các loại cơ bản:

double d = 3.14159265;
int    i = static_cast<int>(d);

4
Tại sao mọi người sẽ viết static_cast<int>(d), mặc dù, khi (int)drất ngắn gọn và dễ đọc hơn? (Ý tôi là trong trường hợp của các loại cơ bản, không phải con trỏ đối tượng.)
Todd Lehman

@ gd1 - Tại sao mọi người sẽ đặt tính nhất quán trên mức dễ đọc? (thực sự nghiêm túc một nửa)
Todd Lehman

2
@ToddLehman: Tôi, cho rằng việc tạo một ngoại lệ cho một số loại nhất định chỉ vì chúng đặc biệt đối với bạn không có ý nghĩa gì với tôi, và tôi cũng không đồng ý với ý kiến ​​rất dễ đọc của bạn. Ngắn hơn không có nghĩa là dễ đọc hơn, như tôi thấy từ hình ảnh bạn đã đăng trong một bình luận khác.
gd1

2
static_cast là một quyết định rõ ràng và có ý thức để thực hiện một loại chuyển đổi rất đặc biệt. Do đó, nó làm tăng thêm sự rõ ràng của ý định. Nó cũng rất tiện dụng như một điểm đánh dấu để tìm kiếm các tệp nguồn để chuyển đổi trong bài đánh giá mã, lỗi hoặc nâng cấp bài tập.
Persixty

1
@ToddLehman counterpoint: Tại sao mọi người sẽ viết (int)dkhi int{d}dễ đọc hơn nhiều? Trình xây dựng, hoặc giống như chức năng nếu bạn có (), cú pháp gần như không quá nhanh để chuyển thành một mê cung ác mộng của dấu ngoặc đơn trong các biểu thức phức tạp. Trong trường hợp này, nó sẽ int i{d}thay vì int i = (int)d. IMO tốt hơn nhiều. Điều đó nói rằng, khi tôi chỉ cần một biểu thức tạm thời, tôi sử dụng static_castvà chưa bao giờ sử dụng các hàm tạo của hàm tạo, tôi không nghĩ vậy. Tôi chỉ sử dụng (C)castskhi vội vàng viết gỡ lỗi cout...
underscore_d
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.