Khi nào nên sử dụng static_cast, Dynamic_cast, const_cast và reinterpret_cast?


2496

Các cách sử dụng thích hợp của:

  • static_cast
  • dynamic_cast
  • const_cast
  • reinterpret_cast
  • Diễn viên kiểu C (type)value
  • Diễn viên theo phong cách chức năng type(value)

Làm thế nào để một người quyết định sử dụng trong trường hợp cụ thể?



3
Đối với một số ví dụ cụ thể hữu ích về việc sử dụng các loại phôi khác nhau, bạn có thể kiểm tra câu trả lời đầu tiên cho một câu hỏi tương tự trong chủ đề khác này .
TeaMonkie

2
Bạn có thể tìm thấy câu trả lời thực sự tốt cho câu hỏi của bạn ở trên. Nhưng tôi muốn đặt thêm một điểm ở đây, @ e.James "Không có gì mà các toán tử cast c ++ mới này có thể làm được và cast kiểu c không thể. Chúng được thêm ít nhiều để dễ đọc mã hơn."
BreakBadSP

@BreakBadSP Các phôi mới không chỉ để đọc mã tốt hơn. Họ ở đó để làm cho việc thực hiện những điều nguy hiểm trở nên khó khăn hơn, như bỏ đi const hoặc đúc con trỏ thay vì giá trị của chúng. static_cast có ít khả năng để làm điều gì đó nguy hiểm hơn so với diễn viên kiểu ac!
FourtyTwo

@FourtyTwo đã đồng ý
BreakBadSP

Câu trả lời:


2571

static_castlà diễn viên đầu tiên bạn nên cố gắng sử dụng. Nó thực hiện những việc như chuyển đổi ngầm giữa các loại (ví dụ như intđể float, hoặc con trỏ tới void*), và nó cũng có thể gọi chức năng chuyển đổi rõ ràng (hoặc những tiềm ẩn). Trong nhiều trường hợp, việc nêu rõ ràng static_castlà không cần thiết, nhưng điều quan trọng cần lưu ý là T(something)cú pháp tương đương (T)somethingvà nên tránh (nhiều hơn về điều đó sau). A T(something, something_else)là an toàn, tuy nhiên, và được đảm bảo để gọi hàm tạo.

static_castcũng có thể truyền qua hệ thống phân cấp thừa kế. Không cần thiết khi truyền lên (đối với lớp cơ sở), nhưng khi truyền xuống, nó có thể được sử dụng miễn là nó không truyền qua virtualkế thừa. Tuy nhiên, nó không thực hiện kiểm tra và đó là hành vi không xác định để static_castphân cấp thành một loại không thực sự là loại đối tượng.


const_castcó thể được sử dụng để loại bỏ hoặc thêm constvào một biến; không có diễn viên C ++ nào khác có khả năng loại bỏ nó (thậm chí không reinterpret_cast). Điều quan trọng cần lưu ý là việc sửa đổi một constgiá trị trước đây chỉ không được xác định nếu biến ban đầu là const; nếu bạn sử dụng nó để loại constbỏ một tham chiếu đến một cái gì đó không được khai báo const, nó an toàn. Điều này có thể hữu ích khi quá tải các chức năng thành viên dựa trên const, ví dụ. Nó cũng có thể được sử dụng để thêm constvào một đối tượng, chẳng hạn như để gọi quá tải hàm thành viên.

const_castcũng hoạt động tương tự volatile, mặc dù điều đó ít phổ biến hơn.


dynamic_castđược sử dụng riêng để xử lý đa hình. Bạn có thể truyền một con trỏ hoặc tham chiếu đến bất kỳ loại đa hình nào cho bất kỳ loại lớp nào khác (một loại đa hình có ít nhất một hàm ảo, được khai báo hoặc kế thừa). Bạn có thể sử dụng nó cho nhiều thứ hơn là chỉ truyền xuống - bạn có thể bỏ sang một bên hoặc thậm chí lên một chuỗi khác. Các dynamic_castsẽ tìm ra các đối tượng mong muốn và gửi lại nếu có thể. Nếu không thể, nó sẽ trả về nullptrtrong trường hợp con trỏ hoặc ném std::bad_casttrong trường hợp tham chiếu.

dynamic_castcó một số hạn chế, mặc dù. Nó không hoạt động nếu có nhiều đối tượng cùng loại trong hệ thống phân cấp thừa kế (cái gọi là 'viên kim cương đáng sợ') và bạn không sử dụng tính virtualkế thừa. Nó cũng chỉ có thể đi qua thừa kế công cộng - nó sẽ luôn luôn không đi qua protectedhoặc privatethừa kế. Điều này hiếm khi là một vấn đề, tuy nhiên, vì các hình thức thừa kế như vậy là rất hiếm.


reinterpret_castlà diễn viên nguy hiểm nhất, và nên được sử dụng rất ít. Nó biến một loại trực tiếp thành loại khác - chẳng hạn như truyền giá trị từ con trỏ này sang con trỏ khác hoặc lưu trữ một con trỏ trong một inthoặc tất cả các loại điều khó chịu khác. Phần lớn, đảm bảo duy nhất bạn nhận được reinterpret_castlà thông thường nếu bạn đưa kết quả trở lại loại ban đầu, bạn sẽ nhận được cùng một giá trị (nhưng không phải nếu loại trung gian nhỏ hơn loại ban đầu). Có một số chuyển đổi reinterpret_castkhông thể làm quá. Nó được sử dụng chủ yếu cho các chuyển đổi và thao tác bit đặc biệt kỳ lạ, như biến luồng dữ liệu thô thành dữ liệu thực tế hoặc lưu trữ dữ liệu trong các bit thấp của con trỏ thành dữ liệu được căn chỉnh.


Diễn viên kiểu Cdiễn viên kiểu chức năng là các diễn viên sử dụng (type)objecthoặc type(object), tương ứng, và tương đương về chức năng. Chúng được định nghĩa là lần đầu tiên sau đây thành công:

  • const_cast
  • static_cast (mặc dù bỏ qua các hạn chế truy cập)
  • static_cast (xem ở trên), sau đó const_cast
  • reinterpret_cast
  • reinterpret_cast, sau đó const_cast

Do đó, nó có thể được sử dụng để thay thế cho các diễn viên khác trong một số trường hợp, nhưng có thể cực kỳ nguy hiểm vì khả năng chuyển thành a reinterpret_castvà nên được ưu tiên khi sử dụng phép đúc rõ ràng, trừ khi bạn chắc chắn static_castsẽ thành công hoặc reinterpret_castsẽ thất bại . Ngay cả sau đó, xem xét các tùy chọn dài hơn, rõ ràng hơn.

Các diễn viên kiểu C cũng bỏ qua kiểm soát truy cập khi thực hiện a static_cast, điều đó có nghĩa là họ có khả năng thực hiện một thao tác mà không có diễn viên nào khác có thể làm được. Tuy nhiên, điều này chủ yếu là một loại bùn, và trong suy nghĩ của tôi chỉ là một lý do khác để tránh các diễn viên kiểu C.


17
Dynamic_cast chỉ dành cho các loại đa hình. bạn chỉ cần sử dụng nó khi bạn chuyển sang một lớp dẫn xuất. static_cast chắc chắn là tùy chọn đầu tiên trừ khi bạn đặc biệt cần funcinoality của Dynamic_cast. Nói chung, đó không phải là một "diễn viên kiểm tra loại đạn bạc" kỳ diệu.
jalf

2
Câu trả lời chính xác! Một nhận xét nhanh: static_cast có thể cần thiết để tạo cấu trúc phân cấp trong trường hợp bạn có Derogen * & để chuyển vào Base * &, vì các con trỏ / tham chiếu kép không tự động tạo cấu trúc phân cấp. Tôi đã gặp tình huống như vậy (thẳng thắn, không phổ biến) hai phút trước. ;-)
bartgol

5
* "không có diễn viên C ++ nào khác có khả năng loại bỏ const(thậm chí không reinterpret_cast)" ... thực sự? Thế còn reinterpret_cast<int *>(reinterpret_cast<uintptr_t>(static_cast<int const *>(0)))?
dùng541686

29
Tôi nghĩ một chi tiết quan trọng còn thiếu ở trên là Dynamic_cast có hình phạt hiệu năng trong thời gian chạy so với tĩnh hoặc reinterpret_cast. Điều này rất quan trọng, ví dụ như trong phần mềm thời gian thực.
jfritz42

5
Có thể đáng nói đến, đó reinterpret_castthường là vũ khí được lựa chọn khi xử lý tập hợp các loại dữ liệu mờ của API
Class Skeleton

333

Sử dụng dynamic_castđể chuyển đổi con trỏ / tham chiếu trong hệ thống phân cấp thừa kế.

Sử dụng static_castcho chuyển đổi loại thông thường.

Sử dụng reinterpret_castđể diễn giải lại mức độ thấp của các mẫu bit. Sử dụng hết sức thận trọng.

Sử dụng const_castđể đúc đi const/volatile. Tránh điều này trừ khi bạn bị mắc kẹt khi sử dụng API const-không chính xác.


2
Hãy cẩn thận với Dynamic_cast. Nó phụ thuộc vào RTTI và điều này sẽ không hoạt động như mong đợi trên các ranh giới thư viện dùng chung. Đơn giản là vì bạn xây dựng thư viện chia sẻ và thực thi độc lập trong không có cách chuẩn hóa để đồng bộ hóa RTTI trên các bản dựng khác nhau. Vì lý do này trong thư viện Qt tồn tại qobject_cast <> sử dụng thông tin loại QObject để kiểm tra các loại.
dùng3150128

198

(Rất nhiều lời giải thích lý thuyết và khái niệm đã được đưa ra ở trên)

Dưới đây là một số ví dụ thực tế khi tôi sử dụng static_cast , Dynamic_cast , const_cast , reinterpret_cast .

(Cũng tham khảo điều này để hiểu giải thích: http://www.cplusplus.com/doc/tutorial/typecasting/ )

static_cast:

OnEventData(void* pData)

{
  ......

  //  pData is a void* pData, 

  //  EventData is a structure e.g. 
  //  typedef struct _EventData {
  //  std::string id;
  //  std:: string remote_id;
  //  } EventData;

  // On Some Situation a void pointer *pData
  // has been static_casted as 
  // EventData* pointer 

  EventData *evtdata = static_cast<EventData*>(pData);
  .....
}

động_cast:

void DebugLog::OnMessage(Message *msg)
{
    static DebugMsgData *debug;
    static XYZMsgData *xyz;

    if(debug = dynamic_cast<DebugMsgData*>(msg->pdata)){
        // debug message
    }
    else if(xyz = dynamic_cast<XYZMsgData*>(msg->pdata)){
        // xyz message
    }
    else/* if( ... )*/{
        // ...
    }
}

const_cast:

// *Passwd declared as a const

const unsigned char *Passwd


// on some situation it require to remove its constness

const_cast<unsigned char*>(Passwd)

reinterpret_cast:

typedef unsigned short uint16;

// Read Bytes returns that 2 bytes got read. 

bool ByteBuffer::ReadUInt16(uint16& val) {
  return ReadBytes(reinterpret_cast<char*>(&val), 2);
}

31
Lý thuyết về một số câu trả lời khác là tốt, nhưng vẫn khó hiểu, nhìn thấy những ví dụ này sau khi đọc các câu trả lời khác thực sự làm cho tất cả chúng có ý nghĩa. Đó là không có ví dụ, tôi vẫn không chắc chắn, nhưng với họ, bây giờ tôi chắc chắn về những câu trả lời khác có nghĩa gì.
Solx

1
Về cách sử dụng cuối cùng của reinterpret_cast: đây không giống như sử dụng static_cast<char*>(&val)?
Lorenzo Belli

3
@LorenzoBelli Tất nhiên là không. Bạn đã thử à? Cái sau không hợp lệ C ++ và khối biên dịch. static_castchỉ hoạt động giữa các loại với các chuyển đổi được xác định, quan hệ rõ ràng theo kế thừa hoặc đến / từ void *. Đối với tất cả mọi thứ khác, có phôi khác. reinterpret castđối với bất kỳ char *loại nào cũng được phép cho phép đọc đại diện của bất kỳ đối tượng nào - và một trong những trường hợp duy nhất mà từ khóa đó hữu ích, không phải là một trình tạo tràn lan của hành vi triển khai- / không xác định. Nhưng điều này không được coi là chuyển đổi 'bình thường', do đó không được phép (thường) rất bảo thủ static_cast.
gạch dưới

2
reinterpret_cast khá phổ biến khi bạn làm việc với phần mềm hệ thống như cơ sở dữ liệu. Hầu hết các trường hợp bạn viết trình quản lý trang của riêng bạn mà không biết loại dữ liệu được lưu trữ trong trang là gì và chỉ trả về một con trỏ trống. Lên đến cấp độ cao hơn để thực hiện một diễn viên diễn giải lại và suy luận nó như bất cứ điều gì họ muốn.
Sohaib

1
Ví dụ const_cast thể hiện Hành vi không xác định. Một biến được khai báo là const không thể là de-const-ed. Tuy nhiên, một biến được khai báo là không phải const được truyền cho một hàm lấy tham chiếu const có thể trong hàm đó có thể được định nghĩa lại mà không phải là UB.
Johann Gerell

99

Nó có thể hữu ích nếu bạn biết một chút nội bộ ...

tĩnh_cast

  • Trình biên dịch C ++ đã biết cách chuyển đổi giữa các loại bộ chia tỷ lệ như float sang int. Sử dụng static_castcho họ.
  • Khi bạn hỏi biên dịch để chuyển đổi từ loại Ađể B, static_castcuộc gọi Bconstructor 's đi qua Anhư param. Ngoài ra, Acó thể có một toán tử chuyển đổi (tức là A::operator B()). Nếu Bkhông có hàm tạo như vậy hoặc Akhông có toán tử chuyển đổi, thì bạn sẽ gặp lỗi thời gian biên dịch.
  • Truyền từ A*để B*luôn thành công nếu A và B nằm trong hệ thống phân cấp thừa kế (hoặc void) nếu không bạn sẽ gặp lỗi biên dịch.
  • Gotcha : Nếu bạn chuyển con trỏ cơ sở sang con trỏ dẫn xuất nhưng nếu đối tượng thực tế không thực sự là loại dẫn xuất thì bạn không gặp lỗi. Bạn nhận được con trỏ xấu và rất có thể là một segfault khi chạy. Cùng đi cho A&đến B&.
  • Gotcha : Truyền từ Derogen sang Base hoặc viceversa tạo bản sao mới ! Đối với những người đến từ C # / Java, đây có thể là một bất ngờ lớn vì kết quả về cơ bản là một đối tượng bị cắt nhỏ được tạo ra từ Derogen.

động_cast

  • Dynamic_cast sử dụng thông tin loại thời gian chạy để tìm hiểu xem cast có hợp lệ không. Ví dụ, (Base*)để (Derived*)có thể thất bại nếu con trỏ không phải là thực sự là loại có nguồn gốc.
  • Điều này có nghĩa, Dynamic_cast rất đắt so với static_cast!
  • Đối A*với B*, nếu cast không hợp lệ thì Dynamic_cast sẽ trả về nullptr.
  • Đối A&với B&nếu cast không hợp lệ thì Dynamic_cast sẽ ném ngoại lệ bad_cast.
  • Không giống như các diễn viên khác, có thời gian chạy.

const_cast

  • Trong khi static_cast có thể không tạo thành const, nó không thể đi theo hướng khác. Const_cast có thể làm cả hai cách.
  • Một ví dụ cho thấy điều này có ích là lặp qua một số container giống như set<T>chỉ trả về các phần tử của nó dưới dạng const để đảm bảo bạn không thay đổi khóa của nó. Tuy nhiên, nếu mục đích của bạn là sửa đổi các thành viên không quan trọng của đối tượng thì nó sẽ ổn thôi. Bạn có thể sử dụng const_cast để loại bỏ constness.
  • Một ví dụ khác là khi bạn muốn thực hiện T& SomeClass::foo()cũng như const T& SomeClass::foo() const. Để tránh trùng lặp mã, bạn có thể áp dụng const_cast để trả về giá trị của hàm này từ hàm khác.

reinterpret_cast

  • Điều này về cơ bản nói rằng lấy các byte này tại vị trí bộ nhớ này và nghĩ về nó như là đối tượng đã cho.
  • Ví dụ: bạn có thể tải 4 byte float đến 4 byte int để xem các bit trong float trông như thế nào.
  • Rõ ràng, nếu dữ liệu không đúng với loại, bạn có thể bị segfault.
  • Không có chi phí thời gian chạy cho diễn viên này.

Tôi đã thêm thông tin toán tử chuyển đổi, nhưng cũng có một vài điều khác cần được khắc phục và tôi không cảm thấy thoải mái khi cập nhật thông tin này quá nhiều. Các mục là: 1. If you cast base pointer to derived pointer but if actual object is not really derived type then you don't get error. You get bad pointer and segfault at runtime.Bạn nhận được UB có thể dẫn đến một segfault khi chạy nếu bạn may mắn. 2. phôi động cũng có thể được sử dụng trong đúc chéo. 3. Các phôi phôi có thể dẫn đến UB trong một số trường hợp. Sử dụng mutablecó thể là một lựa chọn tốt hơn để thực hiện hằng số logic.
Adrian

1
@Adrian bạn đúng trong tất cả các tính. Câu trả lời được viết cho những người ở cấp độ mới bắt đầu hoặc ít hơn và tôi không muốn áp đảo họ với tất cả các biến chứng khác đi kèm mutable, đúc chéo, v.v.
Shital Shah

16

Điều này có trả lời câu hỏi của bạn không?

Tôi chưa bao giờ sử dụng reinterpret_cast, và tự hỏi liệu chạy vào một trường hợp cần nó không phải là một mùi của thiết kế xấu. Trong cơ sở mã tôi làm việc dynamic_castđược sử dụng rất nhiều. Sự khác biệt với static_castlà việc dynamic_castkiểm tra thời gian chạy có thể (an toàn hơn) hoặc có thể không (chi phí cao hơn) là những gì bạn muốn (xem msdn ).


3
Tôi đã sử dụng reintinteret_cast cho một mục đích - lấy các bit ra khỏi một đôi (cùng kích thước dài như trên nền tảng của tôi).
Joshua

2
reinterpret_cast là cần thiết, ví dụ như để làm việc với các đối tượng COM. CoCreateInstance () có tham số đầu ra loại void ** (tham số cuối cùng), trong đó bạn sẽ vượt qua con trỏ được khai báo là ví dụ "INetFwPolicy2 * pNetFwPolicy2". Để làm điều đó, bạn cần viết một cái gì đó như reinterpret_cast <void **> (& pNetFwPolicy2).
Serge Rogatch

1
Có lẽ có một cách tiếp cận khác, nhưng tôi sử dụng reinterpret_castđể trích xuất các mẩu dữ liệu ra khỏi một mảng. Ví dụ, nếu tôi có một char*bộ đệm lớn chứa đầy dữ liệu nhị phân được đóng gói mà tôi cần di chuyển qua và nhận các nguyên hàm riêng lẻ thuộc các loại khác nhau. Một cái gì đó như thế này:template<class ValType> unsigned int readValFromAddress(char* addr, ValType& val) { /*On platforms other than x86(_64) this could do unaligned reads, which could be bad*/ val = (*(reinterpret_cast<ValType*>(addr))); return sizeof(ValType); }
James Matta

Tôi chưa bao giờ sử dụng reinterpret_cast, không có nhiều sử dụng cho nó.
Pika Phù thủy cá voi

Cá nhân tôi chỉ thấy reinterpret_castđược sử dụng cho một lý do. Tôi đã thấy dữ liệu đối tượng thô được lưu trữ vào kiểu dữ liệu "blob" trong cơ sở dữ liệu, sau đó khi dữ liệu được truy xuất từ ​​cơ sở dữ liệu, reinterpret_castđược sử dụng để biến dữ liệu thô này thành đối tượng.
ImaginaryHuman072889

15

Ngoài các câu trả lời khác cho đến nay, đây là ví dụ không rõ ràng khi static_castkhông đủ để điều đó reinterpret_castlà cần thiết. Giả sử có một hàm trong một tham số đầu ra trả về các con trỏ tới các đối tượng của các lớp khác nhau (không chia sẻ một lớp cơ sở chung). Một ví dụ thực tế của chức năng này là CoCreateInstance()(xem tham số cuối cùng, trên thực tế void**). Giả sử bạn yêu cầu lớp đối tượng cụ thể từ hàm này, để bạn biết trước loại cho con trỏ (mà bạn thường làm cho các đối tượng COM). Trong trường hợp này, bạn không thể bỏ con trỏ tới con trỏ của mình void**bằng static_cast: bạn cần reinterpret_cast<void**>(&yourPointer).

Trong mã:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    //static_cast<void**>(&pNetFwPolicy2) would give a compile error
    reinterpret_cast<void**>(&pNetFwPolicy2) );

Tuy nhiên, static_casthoạt động cho các con trỏ đơn giản (không phải con trỏ tới con trỏ), do đó, đoạn mã trên có thể được viết lại để tránh reinterpret_cast(với giá của một biến phụ) theo cách sau:

#include <windows.h>
#include <netfw.h>
.....
INetFwPolicy2* pNetFwPolicy2 = nullptr;
void* tmp = nullptr;
HRESULT hr = CoCreateInstance(__uuidof(NetFwPolicy2), nullptr,
    CLSCTX_INPROC_SERVER, __uuidof(INetFwPolicy2),
    &tmp );
pNetFwPolicy2 = static_cast<INetFwPolicy2*>(tmp);

Nó sẽ không hoạt động giống như &static_cast<void*>(pNetFwPolicy2)thay vì static_cast<void**>(&pNetFwPolicy2)?
jp48

9

Trong khi các câu trả lời khác mô tả độc đáo tất cả sự khác biệt giữa các phôi C ++, tôi muốn thêm một ghi chú ngắn tại sao bạn không nên sử dụng phôi kiểu C (Type) varType(var) .

Đối với người mới bắt đầu C ++, các kiểu phôi C trông giống như hoạt động của superset so với các phôi C ++ (static_cast <> (), Dynamic_cast <> (), const_cast <> (), reinterpret_cast <> ()) và ai đó có thể thích chúng hơn các phôi C ++ . Trong thực tế, diễn viên kiểu C là superset và ngắn hơn để viết.

Vấn đề chính của dàn diễn viên theo phong cách C là họ che giấu ý định thực sự của nhà phát triển. Các phôi kiểu C có thể thực hiện hầu như tất cả các kiểu truyền từ các phôi thông thường an toàn được thực hiện bởi static_cast <> () và Dynamic_cast <> () đến các phôi có khả năng nguy hiểm như const_cast <> (), trong đó công cụ sửa đổi const có thể được loại bỏ để các biến const có thể được sửa đổi và reinterpret_cast <> () thậm chí có thể diễn giải lại các giá trị nguyên cho con trỏ.

Đây là mẫu.

int a=rand(); // Random number.

int* pa1=reinterpret_cast<int*>(a); // OK. Here developer clearly expressed he wanted to do this potentially dangerous operation.

int* pa2=static_cast<int*>(a); // Compiler error.
int* pa3=dynamic_cast<int*>(a); // Compiler error.

int* pa4=(int*) a; // OK. C-style cast can do such cast. The question is if it was intentional or developer just did some typo.

*pa4=5; // Program crashes.

Lý do chính tại sao các diễn viên C ++ được thêm vào ngôn ngữ là để cho phép một nhà phát triển làm rõ ý định của anh ta - tại sao anh ta sẽ thực hiện vai diễn đó. Bằng cách sử dụng các kiểu phôi C hoàn toàn hợp lệ trong C ++, bạn đang làm cho mã của mình ít đọc hơn và dễ bị lỗi hơn, đặc biệt là đối với các nhà phát triển khác không tạo mã của bạn. Vì vậy, để làm cho mã của bạn dễ đọc và rõ ràng hơn, bạn nên luôn thích các phôi C ++ hơn các phôi kiểu C.

Dưới đây là một trích dẫn ngắn từ cuốn sách của Bjarne Stroustrup (tác giả của C ++) Cuốn sách Ngôn ngữ lập trình C ++ phiên bản thứ 4 - trang 302.

Truyền kiểu C này nguy hiểm hơn nhiều so với các toán tử chuyển đổi được đặt tên vì ký hiệu khó phát hiện hơn trong một chương trình lớn và loại chuyển đổi mà lập trình viên dự định không rõ ràng.


5

Để hiểu, hãy xem xét đoạn mã dưới đây:

struct Foo{};
struct Bar{};

int main(int argc, char** argv)
{
    Foo* f = new Foo;

    Bar* b1 = f;                              // (1)
    Bar* b2 = static_cast<Bar*>(f);           // (2)
    Bar* b3 = dynamic_cast<Bar*>(f);          // (3)
    Bar* b4 = reinterpret_cast<Bar*>(f);      // (4)
    Bar* b5 = const_cast<Bar*>(f);            // (5)

    return 0;
}

Chỉ dòng (4) biên dịch không có lỗi. Chỉ reinterpret_cast có thể được sử dụng để chuyển đổi một con trỏ thành một đối tượng thành một con trỏ thành một loại đối tượng không liên quan.

Một điều cần lưu ý là: Dynamic_cast sẽ thất bại trong thời gian chạy, tuy nhiên trên hầu hết các trình biên dịch, nó cũng sẽ không biên dịch được vì không có các hàm ảo trong cấu trúc của con trỏ được truyền, có nghĩa là Dynamic_cast sẽ chỉ hoạt động với các con trỏ lớp đa hình .

Khi nào nên sử dụng C ++ cast :

  • Sử dụng static_cast tương đương với một kiểu đúc C thực hiện chuyển đổi giá trị hoặc khi chúng ta cần tăng một cách rõ ràng một con trỏ từ một lớp sang siêu lớp của nó.
  • Sử dụng const_cast để xóa vòng loại const.
  • Sử dụng reinterpret_cast để thực hiện chuyển đổi không an toàn các loại con trỏ đến và từ số nguyên và các loại con trỏ khác. Chỉ sử dụng điều này nếu chúng tôi biết những gì chúng tôi đang làm và chúng tôi hiểu các vấn đề răng cưa.

3

static_castđấu dynamic_castvớireinterpret_cast nội bộ xem trên một downcast / upcast

Trong câu trả lời này, tôi muốn so sánh ba cơ chế này trên một ví dụ upcast / downcast cụ thể và phân tích những gì xảy ra với các con trỏ / bộ nhớ / lắp ráp cơ bản để đưa ra một sự hiểu biết cụ thể về cách chúng so sánh.

Tôi tin rằng điều này sẽ mang đến một trực giác tốt về cách các diễn viên đó khác nhau:

  • static_cast: có một địa chỉ bù vào thời gian chạy (tác động thời gian chạy thấp) và không có kiểm tra an toàn nào cho thấy một downcast là chính xác.

  • dyanamic_cast: thực hiện cùng một địa chỉ bù vào thời gian chạy như thế nào static_cast, nhưng cũng như một kiểm tra an toàn đắt tiền rằng một downcast là chính xác bằng RTTI.

    Kiểm tra an toàn này cho phép bạn truy vấn nếu một con trỏ lớp cơ sở thuộc loại đã cho trong thời gian chạy bằng cách kiểm tra trả về trong nullptrđó chỉ ra một downcast không hợp lệ.

    Do đó, nếu mã của bạn không thể kiểm tra điều đó nullptrvà thực hiện hành động không hủy bỏ hợp lệ, bạn chỉ nên sử dụng static_castthay vì truyền động.

    Nếu hủy bỏ là hành động duy nhất mà mã của bạn có thể thực hiện, có thể bạn chỉ muốn kích hoạt các dynamic_castbản dựng gỡ lỗi ( -NDEBUG) và sử dụng static_castkhác, ví dụ như được thực hiện ở đây , để không làm chậm quá trình chạy nhanh của bạn.

  • reinterpret_cast: không làm gì trong thời gian chạy, thậm chí không bù địa chỉ. Con trỏ phải trỏ chính xác đến đúng loại, thậm chí không có lớp cơ sở hoạt động. Bạn thường không muốn điều này trừ khi các luồng byte thô có liên quan.

Xem xét ví dụ mã sau:

main.cpp

#include <iostream>

struct B1 {
    B1(int int_in_b1) : int_in_b1(int_in_b1) {}
    virtual ~B1() {}
    void f0() {}
    virtual int f1() { return 1; }
    int int_in_b1;
};

struct B2 {
    B2(int int_in_b2) : int_in_b2(int_in_b2) {}
    virtual ~B2() {}
    virtual int f2() { return 2; }
    int int_in_b2;
};

struct D : public B1, public B2 {
    D(int int_in_b1, int int_in_b2, int int_in_d)
        : B1(int_in_b1), B2(int_in_b2), int_in_d(int_in_d) {}
    void d() {}
    int f2() { return 3; }
    int int_in_d;
};

int main() {
    B2 *b2s[2];
    B2 b2{11};
    D *dp;
    D d{1, 2, 3};

    // The memory layout must support the virtual method call use case.
    b2s[0] = &b2;
    // An upcast is an implicit static_cast<>().
    b2s[1] = &d;
    std::cout << "&d           " << &d           << std::endl;
    std::cout << "b2s[0]       " << b2s[0]       << std::endl;
    std::cout << "b2s[1]       " << b2s[1]       << std::endl;
    std::cout << "b2s[0]->f2() " << b2s[0]->f2() << std::endl;
    std::cout << "b2s[1]->f2() " << b2s[1]->f2() << std::endl;

    // Now for some downcasts.

    // Cannot be done implicitly
    // error: invalid conversion from ‘B2*’ to ‘D*’ [-fpermissive]
    // dp = (b2s[0]);

    // Undefined behaviour to an unrelated memory address because this is a B2, not D.
    dp = static_cast<D*>(b2s[0]);
    std::cout << "static_cast<D*>(b2s[0])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[0])->int_in_d  " << dp->int_in_d << std::endl;

    // OK
    dp = static_cast<D*>(b2s[1]);
    std::cout << "static_cast<D*>(b2s[1])            " << dp           << std::endl;
    std::cout << "static_cast<D*>(b2s[1])->int_in_d  " << dp->int_in_d << std::endl;

    // Segfault because dp is nullptr.
    dp = dynamic_cast<D*>(b2s[0]);
    std::cout << "dynamic_cast<D*>(b2s[0])           " << dp           << std::endl;
    //std::cout << "dynamic_cast<D*>(b2s[0])->int_in_d " << dp->int_in_d << std::endl;

    // OK
    dp = dynamic_cast<D*>(b2s[1]);
    std::cout << "dynamic_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "dynamic_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;

    // Undefined behaviour to an unrelated memory address because this
    // did not calculate the offset to get from B2* to D*.
    dp = reinterpret_cast<D*>(b2s[1]);
    std::cout << "reinterpret_cast<D*>(b2s[1])           " << dp           << std::endl;
    std::cout << "reinterpret_cast<D*>(b2s[1])->int_in_d " << dp->int_in_d << std::endl;
}

Biên dịch, chạy và tháo rời với:

g++ -ggdb3 -O0 -std=c++11 -Wall -Wextra -pedantic -o main.out main.cpp
setarch `uname -m` -R ./main.out
gdb -batch -ex "disassemble/rs main" main.out

nơi setarchđược sử dụng để vô hiệu hóa ASLR để giúp so sánh các lần chạy dễ dàng hơn.

Sản lượng có thể:

&d           0x7fffffffc930
b2s[0]       0x7fffffffc920
b2s[1]       0x7fffffffc940
b2s[0]->f2() 2
b2s[1]->f2() 3
static_cast<D*>(b2s[0])            0x7fffffffc910
static_cast<D*>(b2s[0])->int_in_d  1
static_cast<D*>(b2s[1])            0x7fffffffc930
static_cast<D*>(b2s[1])->int_in_d  3
dynamic_cast<D*>(b2s[0])           0
dynamic_cast<D*>(b2s[1])           0x7fffffffc930
dynamic_cast<D*>(b2s[1])->int_in_d 3
reinterpret_cast<D*>(b2s[1])           0x7fffffffc940
reinterpret_cast<D*>(b2s[1])->int_in_d 32767

Bây giờ, như đã đề cập tại: https://en.wikipedia.org/wiki/Virtual_method_table để hỗ trợ các cuộc gọi phương thức ảo một cách hiệu quả, cấu trúc dữ liệu bộ nhớ Dphải trông giống như:

B1:
  +0: pointer to virtual method table of B1
  +4: value of int_in_b1

B2:
  +0: pointer to virtual method table of B2
  +4: value of int_in_b2

D:
  +0: pointer to virtual method table of D (for B1)
  +4: value of int_in_b1
  +8: pointer to virtual method table of D (for B2)
 +12: value of int_in_b2
 +16: value of int_in_d

Thực tế quan trọng là cấu trúc dữ liệu bộ nhớ Dchứa bên trong cấu trúc bộ nhớ tương thích với cấu trúc bộ nhớ trong B1B2bên trong.

Vì vậy, chúng tôi đi đến kết luận quan trọng:

một upcast hoặc downcast chỉ cần thay đổi giá trị con trỏ theo một giá trị được biết tại thời điểm biên dịch

Theo cách này, khi Dđược chuyển đến mảng loại cơ sở, kiểu cast thực sự tính toán phần bù đó và chỉ ra thứ gì đó trông giống hệt như một giá trị B2trong bộ nhớ:

b2s[1] = &d;

ngoại trừ việc cái này có vtable Dthay cho B2, và do đó tất cả các cuộc gọi ảo hoạt động trong suốt.

Bây giờ, cuối cùng chúng ta có thể quay lại kiểu đúc và phân tích ví dụ cụ thể của chúng ta.

Từ đầu ra tiêu chuẩn, chúng ta thấy:

&d           0x7fffffffc930
b2s[1]       0x7fffffffc940

Do đó, ẩn static_castđược thực hiện ở đó đã tính toán chính xác phần bù từ Dcấu trúc dữ liệu đầy đủ tại 0x7fffffffc930 đến mức B2tương tự ở mức 0x7fffffffc940. Chúng tôi cũng suy luận rằng những gì nằm giữa 0x7fffffffc930 và 0x7fffffffc940 có thể là B1dữ liệu và vtable.

Sau đó, trên các phần downcast, giờ đây thật dễ hiểu làm thế nào những phần không hợp lệ thất bại và tại sao:

  • static_cast<D*>(b2s[0]) 0x7fffffffc910: trình biên dịch chỉ tăng 0x10 tại các byte thời gian biên dịch để thử và chuyển từ a B2sang chứaD

    Nhưng vì b2s[0]không phải là mộtD nên giờ nó chỉ đến một vùng nhớ không xác định.

    Việc tháo gỡ là:

    49          dp = static_cast<D*>(b2s[0]);
       0x0000000000000fc8 <+414>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fcc <+418>:   48 85 c0        test   %rax,%rax
       0x0000000000000fcf <+421>:   74 0a   je     0xfdb <main()+433>
       0x0000000000000fd1 <+423>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x0000000000000fd5 <+427>:   48 83 e8 10     sub    $0x10,%rax
       0x0000000000000fd9 <+431>:   eb 05   jmp    0xfe0 <main()+438>
       0x0000000000000fdb <+433>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000000fe0 <+438>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    vì vậy chúng tôi thấy rằng GCC không:

    • kiểm tra xem con trỏ có phải là NULL không và nếu có trả về NULL
    • mặt khác, trừ 0x10 từ nó để đạt đến Dcái không tồn tại
  • dynamic_cast<D*>(b2s[0]) 0: C ++ thực sự thấy rằng dàn diễn viên không hợp lệ và được trả lại nullptr!

    Không có cách nào có thể được thực hiện tại thời điểm biên dịch và chúng tôi sẽ xác nhận điều đó từ quá trình tháo gỡ:

    59          dp = dynamic_cast<D*>(b2s[0]);
       0x00000000000010ec <+706>:   48 8b 45 d0     mov    -0x30(%rbp),%rax
       0x00000000000010f0 <+710>:   48 85 c0        test   %rax,%rax
       0x00000000000010f3 <+713>:   74 1d   je     0x1112 <main()+744>
       0x00000000000010f5 <+715>:   b9 10 00 00 00  mov    $0x10,%ecx
       0x00000000000010fa <+720>:   48 8d 15 f7 0b 20 00    lea    0x200bf7(%rip),%rdx        # 0x201cf8 <_ZTI1D>
       0x0000000000001101 <+727>:   48 8d 35 28 0c 20 00    lea    0x200c28(%rip),%rsi        # 0x201d30 <_ZTI2B2>
       0x0000000000001108 <+734>:   48 89 c7        mov    %rax,%rdi
       0x000000000000110b <+737>:   e8 c0 fb ff ff  callq  0xcd0 <__dynamic_cast@plt>
       0x0000000000001110 <+742>:   eb 05   jmp    0x1117 <main()+749>
       0x0000000000001112 <+744>:   b8 00 00 00 00  mov    $0x0,%eax
       0x0000000000001117 <+749>:   48 89 45 98     mov    %rax,-0x68(%rbp)

    Đầu tiên, có một kiểm tra NULL và nó trả về NULL nếu thông số đó là NULL.

    Mặt khác, nó thiết lập một số đối số trong RDX, RSI và RDI và các cuộc gọi __dynamic_cast.

    Bây giờ tôi không đủ kiên nhẫn để phân tích vấn đề này, nhưng như những người khác đã nói, cách duy nhất để làm việc này là __dynamic_casttruy cập vào một số cấu trúc dữ liệu trong bộ nhớ RTTI đại diện cho hệ thống phân cấp lớp.

    Do đó, nó phải bắt đầu từ B2mục nhập cho bảng đó, sau đó đi theo cấu trúc phân cấp lớp này cho đến khi nhận thấy rằng vtable cho một Dkiểu chữ từb2s[0] .

    Đây là lý do tại sao diễn viên reinterpret có khả năng đắt tiền! Dưới đây là một ví dụ trong đó một bản vá lót chuyển đổi dynamic_castthành một static_casttrong một dự án phức tạp đã giảm 33% thời gian chạy! .

  • reinterpret_cast<D*>(b2s[1]) 0x7fffffffc940Điều này chỉ tin tưởng chúng tôi một cách mù quáng: chúng tôi đã nói rằng có một Dđịa chỉ b2s[1]và trình biên dịch không tính toán bù.

    Nhưng điều này là sai, bởi vì D thực sự ở 0x7fffffffc930, ở 0x7fffffffc940 là cấu trúc giống như B2 bên trong D! Vì vậy, rác được truy cập.

    Chúng tôi có thể xác nhận điều này từ -O0hội đồng khủng khiếp chỉ di chuyển giá trị xung quanh:

    70          dp = reinterpret_cast<D*>(b2s[1]);
       0x00000000000011fa <+976>:   48 8b 45 d8     mov    -0x28(%rbp),%rax
       0x00000000000011fe <+980>:   48 89 45 98     mov    %rax,-0x68(%rbp)

Câu hỏi liên quan:

Đã thử nghiệm trên Ubuntu 18.04 amd64, GCC 7.4.0.

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.