Câu hỏi Diễn viên thường xuyên so với static_cast vs. dynamic_cast [duplicate]


Câu hỏi này đã có câu trả lời ở đây:

Tôi đã viết mã C và C ++ trong gần hai mươi năm, nhưng có một khía cạnh của những ngôn ngữ mà tôi chưa bao giờ thực sự hiểu được. Tôi rõ ràng đã sử dụng các phôi thông thường, tức là

MyClass *m = (MyClass *)ptr;

khắp nơi, nhưng dường như có hai loại phôi khác, và tôi không biết sự khác biệt. Sự khác nhau giữa các dòng mã sau là gì?

MyClass *m = (MyClass *)ptr;
MyClass *m = static_cast<MyClass *>(ptr);
MyClass *m = dynamic_cast<MyClass *>(ptr);

1472
2017-08-26 13:20


gốc


Xem thêm <stackoverflow.com/questions/28080/…; - Sam Hasler
Tôi sẽ không gọi di sản C-phong cách đúc một "diễn viên thường xuyên" trong C + +, vì nó là bất cứ điều gì nhưng. Bạn thường không nên sử dụng trong C + +, đặc biệt là với các lớp học, nó chỉ là quá dễ dàng để làm cho những sai lầm với nó. Sử dụng nó là một dấu hiệu của một lập trình viên C, người đã chuyển sang C ++ nhưng chưa hoàn toàn học được C ++. - hyde
làm thế nào có thể một câu hỏi với một câu trả lời là một bản sao của một câu hỏi mà không có một câu trả lời? hơn nữa, câu hỏi này được hỏi sớm hơn là "bản gốc" - Vladp
@Vladp Trong trường hợp bạn vẫn đang tự hỏi, hoặc bất cứ ai khác đang đọc điều này và thắc mắc. (Ngoài ra, đối với hồ sơ, nó không phải là một người kiểm duyệt đóng cửa này, nó là một người dùng với một NULL) - Nic Hartley
FYI the câu hỏi được liên kết có nhiều upvotes hơn và câu trả lời cũng có nhiều upvotes hơn. Câu hỏi được liên kết cũng có một số ví dụ phi lý thuyết tốt. (Ngoài ra, câu hỏi được liên kết không tham chiếu sai cú pháp kiểu chữ C theo kiểu "thường xuyên truyền"). - Trevor Boyd Smith


Các câu trả lời:


static_cast

static_cast được sử dụng cho các trường hợp mà về cơ bản bạn muốn đảo ngược chuyển đổi tiềm ẩn, với một vài hạn chế và bổ sung. static_cast không thực hiện kiểm tra thời gian chạy. Điều này nên được sử dụng nếu bạn biết rằng bạn đề cập đến một đối tượng của một loại cụ thể, và do đó một kiểm tra sẽ là không cần thiết. Thí dụ:

void func(void *data) {
  // Conversion from MyClass* -> void* is implicit
  MyClass *c = static_cast<MyClass*>(data);
  ...
}

int main() {
  MyClass c;
  start_thread(&func, &c)  // func(&c) will be called
      .join();
}

Trong ví dụ này, bạn biết rằng bạn đã vượt qua MyClass và do đó không cần phải kiểm tra thời gian chạy để đảm bảo điều này.

dynamic_cast

dynamic_cast rất hữu ích khi bạn không biết loại đối tượng động là gì. Nó trả về một con trỏ rỗng nếu đối tượng được gọi không chứa kiểu được đúc thành lớp cơ sở (khi bạn truyền đến một tham chiếu, một bad_cast ngoại lệ được ném trong trường hợp đó).

if (JumpStm *j = dynamic_cast<JumpStm*>(&stm)) {
  ...
} else if (ExprStm *e = dynamic_cast<ExprStm*>(&stm)) {
  ...
}

Bạn không thể sử dụng dynamic_cast nếu bạn downcast (cast vào một lớp dẫn xuất) và kiểu đối số không phải là đa hình. Ví dụ, mã sau đây không hợp lệ, bởi vì Base không chứa bất kỳ chức năng ảo nào:

struct Base { };
struct Derived : Base { };
int main() {
  Derived d; Base *b = &d;
  dynamic_cast<Derived*>(b); // Invalid
}

Một "up-cast" (cast vào lớp cơ sở) luôn hợp lệ với cả hai static_cast và dynamic_cast, và cũng không có bất kỳ diễn viên nào, như là một "up-cast" là một chuyển đổi tiềm ẩn.

Cast thường xuyên

Các phôi này cũng được gọi là kiểu đúc C. Một dàn diễn viên kiểu C về cơ bản là giống hệt nhau để thử một loạt các chuỗi C ++, và lấy đĩa C ++ đầu tiên hoạt động, mà không bao giờ xem xét dynamic_cast. Không cần phải nói, điều này mạnh hơn nhiều vì nó kết hợp tất cả const_cast, static_cast và reinterpret_cast, nhưng nó cũng không an toàn, bởi vì nó không sử dụng dynamic_cast.

Ngoài ra, các phôi kiểu C không chỉ cho phép bạn thực hiện điều này, mà còn cho phép bạn truyền một cách an toàn tới một lớp cơ sở riêng, trong khi "tương đương" static_cast trình tự sẽ cung cấp cho bạn một lỗi biên dịch thời gian cho điều đó.

Một số người thích phôi kiểu C vì ngắn gọn của họ. Tôi sử dụng chúng cho chỉ phôi số, và sử dụng phôi C ++ thích hợp khi người dùng xác định loại có liên quan, vì chúng cung cấp kiểm tra chặt chẽ hơn.


1413
2017-08-10 13:50



Xem thêm hai phần bổ sung của boost: boost.org/doc/libs/1_47_0/libs/conversion/… - Neil G
@ JohannesSchaub-litb: Bạn có chắc chắn rằng một dàn diễn viên kiểu C cho phép bạn 'an toàn' truyền sang một lớp cơ sở riêng tư không? Tôi có thể thấy rằng làm việc khi các lớp cơ sở tư nhân là chỉ / cơ sở /, nhưng những gì về ảo / đa thừa kế? Tôi giả sử các diễn viên phong cách C không có thao tác con trỏ. - Joseph Garvin
@ JohannesSchaub-litb là nó đúng là cũng có một số chi phí liên quan đến việc sử dụng các c-phong cách cũ phôi trên C + + phôi? - xcrypt
@ Joseph: Nó sẽ không thực hiện cross-cast một cách chính xác, hoặc bất kỳ trường hợp nào khác cần kiểm tra thời gian chạy (dynamic_cast bắt buộc). Nhưng nó sẽ làm tất cả các điều chỉnh con trỏ giống như static_cast làm. Thừa kế nhiều (không phải ảo) được hỗ trợ tốt và điều chỉnh con trỏ chính xác sẽ được sử dụng. - Ben Voigt
Bạn có thể giải thích chi tiết hơn tại sao downcast trong phần cast động không hợp lệ? giả định Derived có một member m Tôi muốn đạt được, làm thế nào điều này sẽ đạt được, nếu dynamic_cast không phải là một sự lựa chọn? - ted


Tĩnh đúc

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 kiểu C, nhưng hạn chế hơn. Ví dụ, dàn diễn viên 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ì kết quả này trong một con trỏ 4 byte trỏ đến 1 byte bộ nhớ được cấp phát, 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 dàn diễn viên kiểu C, phép đúc 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à pointee 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

Diễn giải lại diễn viên

Để buộc chuyển đổi con trỏ, giống như cách diễn viên kiểu C làm trong nền, diễn giải diễn giải lại sẽ được sử dụng thay thế.

int *r = reinterpret_cast<int*>(&c); // forced conversion

Dàn diễn viên này xử lý chuyển đổi giữa các loại không liên quan nhất định, chẳng hạn như từ một loại con trỏ đến loại con trỏ không tương thích khác. Nó sẽ chỉ thực hiện một bản sao nhị phân của dữ liệu mà không thay đổi mẫu bit cơ bản. Lưu ý rằng kết quả của một hoạt động cấp thấp như vậy là hệ thống cụ thể và do đó không thể di chuyển được. Nó nên được sử dụng thận trọng nếu nó không thể tránh được hoàn toàn.

Dynamic cast

Điều này chỉ được sử dụng để chuyển đổi các con trỏ đối tượng và tham chiếu đối tượng thành các kiểu con trỏ hoặc tham chiếu khác trong hệ thống phân cấp thừa kế. Đây là diễn viên duy nhất đảm bảo rằng đối tượng được trỏ tới có thể được chuyển đổi, bằng cách thực hiện kiểm tra thời gian chạy mà con trỏ đề cập đến một đối tượng hoàn chỉnh của loại đích. Đối với kiểm tra thời gian chạy này để có thể đối tượng phải được đa hình. Tức là, lớp phải xác định hoặc kế thừa ít nhất một hàm ảo. Điều này là do trình biên dịch sẽ chỉ tạo ra thông tin kiểu thời gian chạy cần thiết cho các đối tượng như vậy.

Ví dụ truyền động

Trong ví dụ dưới đây, một con trỏ MyChild được chuyển đổi thành một con trỏ MyBase bằng cách sử dụng một diễn viên động. Chuyển đổi từ gốc sang cơ sở này thành công, bởi vì đối tượng Child bao gồm một đối tượng Base hoàn chỉnh.

class MyBase 
{ 
  public:
  virtual void test() {}
};
class MyChild : public MyBase {};



int main()
{
  MyChild *child = new MyChild();
  MyBase  *base = dynamic_cast<MyBase*>(child); // ok
}

Ví dụ tiếp theo cố gắng chuyển đổi một con trỏ MyBase thành con trỏ MyChild. Vì đối tượng Base không chứa một đối tượng Child hoàn chỉnh, việc chuyển đổi con trỏ này sẽ thất bại. Để chỉ ra điều này, các diễn viên năng động trả về một con trỏ null. Điều này mang lại một cách thuận tiện để kiểm tra xem một chuyển đổi có thành công trong thời gian chạy hay không.

MyBase  *base = new MyBase();
MyChild *child = dynamic_cast<MyChild*>(base);


if (child == 0) 
std::cout << "Null pointer returned";

Nếu một tham chiếu được chuyển đổi thay vì một con trỏ, thì diễn viên động sẽ thất bại bằng cách ném một ngoại lệ bad_cast. Điều này cần phải được xử lý bằng cách sử dụng một câu lệnh try-catch.

#include <exception>
// …  
try
{ 
  MyChild &child = dynamic_cast<MyChild&>(*base);
}
catch(std::bad_cast &e) 
{ 
  std::cout << e.what(); // bad dynamic_cast
}

Dàn động hoặc tĩnh

Lợi thế của việc sử dụng một diễn viên năng động là nó cho phép các lập trình viên kiểm tra xem một chuyển đổi có thành công trong thời gian chạy hay không. Những bất lợi là có một hiệu suất trên không kết hợp với việc kiểm tra này. Vì lý do này, việc sử dụng một phép đúc tĩnh sẽ thích hợp hơn trong ví dụ đầu tiên, bởi vì một chuyển đổi từ gốc sang cơ sở sẽ không bao giờ thất bại.

MyBase *base = static_cast<MyBase*>(child); // ok

Tuy nhiên, trong ví dụ thứ hai, việc chuyển đổi có thể thành công hoặc thất bại. Nó sẽ thất bại nếu đối tượng MyBase chứa một cá thể MyBase và nó sẽ thành công nếu nó chứa một cá thể MyChild. Trong một số trường hợp, điều này có thể không được biết đến cho đến khi chạy. Khi đây là trường hợp diễn động động là một lựa chọn tốt hơn so với đúc tĩnh.

// Succeeds for a MyChild object
MyChild *child = dynamic_cast<MyChild*>(base);

Nếu chuyển đổi cơ bản đến xuất phát đã được thực hiện bằng cách sử dụng một diễn viên tĩnh thay vì một diễn viên năng động chuyển đổi sẽ không có thất bại. Nó sẽ trả về một con trỏ trỏ đến một đối tượng không đầy đủ. Dereferencing như một con trỏ có thể dẫn đến lỗi thời gian chạy.

// Allowed, but invalid
MyChild *child = static_cast<MyChild*>(base);

// Incomplete MyChild object dereferenced
(*child);

Const cast

Điều này chủ yếu được sử dụng để thêm hoặc loại bỏ biến tố const của một biến.

const int myConst = 5;
int *nonConst = const_cast<int*>(&myConst); // removes const

Mặc dù const cast cho phép giá trị của một hằng số được thay đổi, làm như vậy vẫn là mã không hợp lệ có thể gây ra lỗi thời gian chạy. Điều này có thể xảy ra ví dụ nếu hằng số được đặt trong một phần của bộ nhớ chỉ đọc.

*nonConst = 10; // potential run-time error

Const cast thay vào đó được sử dụng chủ yếu khi có một hàm nhận một tham số con trỏ không liên tục, mặc dù nó không thay đổi pointee.

void print(int *p) 
{
   std::cout << *p;
}

Sau đó, hàm này có thể được chuyển một biến cố định bằng cách sử dụng một phép đúc const.

print(&myConst); // error: cannot convert 
                 // const int* to int*

print(nonConst); // allowed

Nguồn và giải thích thêm


118
2017-08-26 13:28



std::bad_cast được định nghĩa trong <typeinfo> - Evgeny
Câu trả lời chính xác! Đã học tấn :) - will.fiset


Bạn nên xem bài viết Lập trình C ++ / Loại đúc.

Nó chứa một mô tả tốt về tất cả các loại diễn viên khác nhau. Những điều sau đây được lấy từ liên kết trên:

const_cast

const_cast (expression) const_cast <> () được sử dụng để thêm / xóa   const (ness) (hoặc biến động-ness) của một biến.

static_cast

static_cast (expression) static_cast <> () được sử dụng để chèn giữa   các loại số nguyên. 'ví dụ.' char-> dài, int-> ngắn, v.v.

Tĩnh đúc cũng được sử dụng để đúc con trỏ đến các loại liên quan, cho   ví dụ đúc void * cho loại thích hợp.

dynamic_cast

Dynamic cast được sử dụng để chuyển đổi con trỏ và tham chiếu trong thời gian chạy,   nói chung với mục đích đúc con trỏ hoặc tham chiếu lên hoặc xuống   một chuỗi kế thừa (hệ thống phân cấp thừa kế).

dynamic_cast (biểu thức)

Loại mục tiêu phải là con trỏ hoặc loại tham chiếu và   biểu thức phải đánh giá con trỏ hoặc tham chiếu. Công việc truyền động   chỉ khi loại đối tượng mà biểu thức đề cập đến là   tương thích với kiểu đích và lớp cơ sở có ít nhất một   chức năng thành viên ảo. Nếu không, và loại biểu thức đang được truyền   là một con trỏ, NULL được trả về, nếu một diễn viên động trên một tham chiếu   thất bại, một ngoại lệ bad_cast được ném. Khi nó không thất bại, năng động   cast trả về một con trỏ hoặc tham chiếu của kiểu đích cho đối tượng   mà biểu thức được gọi.

reinterpret_cast

Việc diễn giải lại diễn xuất chỉ đơn giản là phôi một loại bitwise sang một loại khác. Bất kỳ con trỏ nào   hoặc loại tích phân có thể được đúc với bất kỳ loại nào khác với cách diễn giải lại,   dễ dàng cho phép lạm dụng. Ví dụ, với diễn giải lại diễn viên   có thể, không an toàn, đúc một con trỏ số nguyên đến một con trỏ chuỗi.


71
2017-09-19 17:30





Tránh sử dụng phôi kiểu C.

C-style phôi là một kết hợp của const và diễn giải lại diễn viên, và thật khó để tìm và thay thế trong mã của bạn. Một lập trình viên ứng dụng C ++ nên tránh cast kiểu C.


23
2017-08-26 13:39





FYI, tôi tin rằng Bjarne Stroustrup được trích dẫn khi nói rằng C-style phôi phải được tránh và rằng bạn nên sử dụng static_cast hoặc dynamic_cast nếu có thể.

Câu hỏi thường gặp về phong cách C ++ của Barne Stroustrup

Hãy lấy lời khuyên đó cho những gì bạn sẽ làm. Tôi không phải là một guru của C ++.


21
2017-08-26 13:38



Bjarne Stroustrup ... Đó là người đã cho chúng tôi địa ngục đúc kiểu này?
^ Vâng, bởi vì các phôi C ++ được dán nhãn rõ ràng và cố ý giới hạn trong các vai trò được xác định rõ ràng hơn là "địa ngục" hơn một dàn diễn viên C, mà chỉ mù quáng thử nhiều loại diễn viên cho đến khi bất cứ điều gì hoạt động, bất kể ý nghĩa ... tốt nhất. - underscore_d


C-style phôi conflate const_cast, static_cast, và reinterpret_cast.

Tôi muốn C ++ không có phôi kiểu C. C ++ phôi nổi bật đúng (như họ nên; phôi thường chỉ dẫn làm điều gì đó xấu) và phân biệt đúng giữa các loại chuyển đổi mà phôi thực hiện. Chúng cũng cho phép viết các hàm tương tự, ví dụ: boost :: lexical_cast, khá tốt từ quan điểm nhất quán.


10
2017-08-26 13:26





dynamic_castcó kiểm tra kiểu thời gian chạy và chỉ hoạt động với các tham chiếu và con trỏ, trong khi static_cast không cung cấp kiểm tra kiểu thời gian chạy. Để biết thông tin đầy đủ, hãy xem bài viết MSDN toán tử static_cast.


8
2018-02-05 17:10