[英]assigning all properties of object of base class to derived one
我從基類B派生了類D,如下所示:
class D : public B
{
//staff or nothing
}
欲處理接收到的指針B* b
,在聲明的方式D* d
,以及初始化*d
與從所有相同的屬性值*b
。 如果我無權更改B
實現,有什么可能或最好的方法? 就像是
// I have pointer to B object, B* b received as a result of some function
class D : public B
{
//staff or nothing
}
D *d; //declaration
// I want to initialize d, something like d=b (if that would be legal)
// or d = static_cast <D*>(b);
謝謝
符號:
B b;
B* pb = &b;
D* pd;
如果您有指針pb
並希望有一個指針pd
,其中*pd
具有與*pb
相同的(值)數據成員,則必須將數據從*pb
復制或移動到某個(內存)位置並讓pd
指向該位置。
C ++不允許從&b
到pd
的轉換,既不能隱式( pd = &b
)也不能顯式( pd = static_cast<D*>(&b)
),而不會導致未定義的行為。 未定義的行為非常糟糕,因為您無法保證接下來會發生什么(崩潰,數據損壞,heisenbugs等)。
static_cast
ptr轉換 讓我們從標准中引入一些名稱,以使事情更清楚:
B* pb = &d;
然后將靜態類型的*pb
是B
而動態類型的*pb
是D
。 靜態類型基本上是編譯器看到的,而動態類型則是運行時實際存在的類型。
以下轉換很好:
D d;
pb = &d; // `pb` points to `B`-type subobject of `d`
pd = static_cast<D*>(pb); // reverts the cast above to get the original `&d`
最后一行是在殼體細*pb
具有動態型D
(或派生類型的D
)。 其他,不確定的行為。 這就是為什么要使用dynamic_cast
:
pd = dynamic_cast<D*>(pb);
同樣,如果*pb
是動態類型D
,則一切都很好(與上面相同)。 但是,如果*pb
不是動態類型D
,則dynamic_cast
返回空指針值( nullptr
)。 這樣,沒有未定義的行為,您可以檢查pd
現在是否為nullptr
。 由於dynamic_cast
的速度實際上比static_cast
慢,因此如果您真的知道轉換將成功,則可以改用static_cast
( 請參見Qt示例 )。
現在,如果您希望擁有一個指針pd
,以使所有數據成員(“屬性”)都與&b
的數據成員相同,該怎么辦? 您必須創建一個D
類型的對象,並將數據從&b
復制或移動到該新對象。 這是Subaru Tashiro在方法2中提出的,例如:
class D : public B
{
public:
// copy:
D(B const& b) : B(b) {} // if you cannot use B-copy-ctor than have to provide own definition
// move:
D(B&& b) : B(b) {} // same issue as above
};
B b;
B* pb = &b;
D d1(*pb); // this is how you can copy
D d2( std::move(*pb) ); // this is how you can move
D* pd = &d1; // or = &d2;
請注意,復制行和移動行都創建了一個新的D
型對象,用於存儲復制/移動的數據。您無需同時提供復制和移動支持。
還有其他可能性,例如包裝B
型對象,但它們與派生類的方法不同。
pd = &b
不好 D
子類引入的數據成員是有問題的。 如果D
類添加了任何數據成員,那么C ++ /編譯器應如何知道應如何初始化它們? 它不能僅僅讓它們未初始化,因為這很容易出錯(考慮類不變式)。 內存問題。 示例:想象一個D
型對象在內存中看起來像這樣:
|DD[BBBB]DDDDDDDD| ^start of B sub-object ^start of D object
現在,當您執行pd = static_cast<D*>(pb)
,包含在pb
的地址將被解釋為B子對象的開始,並減少2個字節以獲取D object的開始 。
b
對象分配了空間,因此只能保證可以訪問(char*)pb
到(char*)pb + sizeof(b)
的內存(char*)pb
考慮b
內的對齊方式)。 在(char*)pb
或之后(char*)pb + sizeof(b)
之后訪問內存可能會導致錯誤,例如,CPU抱怨您正在訪問尚未映射到物理內存的虛擬內存。 更有可能在(char*)pb
和之后(char*)pb + sizeof(b)
之后還有其他有意義的數據。 通過寫入D
類中引入的static_cast<D*>(pb)
任何數據成員,您可以破壞此其他數據(不確定其他數據成員)。 *pd
訪問任何成員時,您可能會遇到對齊問題,例如,如果編譯器假定所有D類對象都以2字節為邊界開始,而所有B類對象都以4字節為邊界開始(易碎和病理性,但可能的問題)。 D
有一個vtable,則不會通過指針轉換對其進行初始化。 因此,調用已在D
類中引入的static_cast<D*>(pb)
任何虛擬方法不是一個好主意。 dynamic_casts
,因此受影響的不僅是typeid
。 *pd
的B
子對象與某些沒有問題的編譯器/ C ++實現一起使用,也無法保證它將與所有編譯器/版本一起使用。 以上幾點是我想到的一些問題,但我絕不是專家。 本質上, 無法預測/無法保證執行此轉換並使用結果pd
的B
子對象時會發生什么 。 最后說明:
static_cast
參考:5.2.9 / 2,最后2個句子 reinterpret_cast
和很多注釋,以表明您知道自己在做什么,這確實是黑客。 方法1:可以使用static_cast
使用基類初始化派生類,但這將導致派生對象不完整。
static_cast不僅可以在指向相關類的指針之間執行轉換,不僅可以從派生類到其基類,還可以從基類到其派生類。 這樣可以確保如果轉換了正確的對象,至少這些類是兼容的,但是在運行時不會執行安全檢查來檢查轉換的對象是否實際上是目標類型的完整對象。 因此,程序員必須確保轉換是安全的。
請參閱以下示例:
在這里,我們編寫一個具有稱為“ bInt”的公共變量的基類
class B {
public:
int bInt;
};
在這里,我們創建一個子類,該子類也具有自己的變量“ dInt”
class D: public B {
public:
int dInt;
};
在這里,我們有了基類的新實例。 我們初始化基礎對象的變量。
B * baseObj = new B;
baseObj->bInt = 1;
在這里,我們使用基類通過static_cast
聲明和初始化派生類。 請注意,此時*derivedObj
不完整。 具體來說, derivedObj->dInt
具有未定義的值。
D * derivedObj = static_cast<D*>(baseObj);
由於D是從B派生的,因此它還具有bInt
變量。 並且由於我們使用static_cast
來初始化*derivedObj
,因此它的bInt
值也與*baseObj
的bInt
,因此它們是相等的。
if(baseObj->bInt == derivedObj->bInt)
{
display("it's equal");
}
但是,由於基類沒有dInt
變量,因此該變量將保持未初始化狀態,並且該過程的結果不確定。
int myNum = derivedObj->dInt;
如果計划使用static_cast,請確保初始化派生類的成員。 無論如何,這是一個好習慣。
使用static_cast時,您不需要知道B的所有成員。D自動具有B的所有成員值。 但是,您嘗試做的事情很危險,因為您正在創建不完整的對象。
方法2:如果確實需要繼承B,則將D的構造函數編寫為采用B *並通過復制將其初始化為B的構造函數,如下所示:
D(const B &pass) : B(pass)
因此,當您要聲明和初始化D類型的對象時,這就是您的操作方式。
B *baseObj = new B;
D *derivedObj = new D(*baseObj);
因此,總而言之,您有兩種選擇:
這兩種方法都具有相同的結果,不同之處在於程序在后台如何執行此操作。
不幸的是,在您的示例中,使用d=b
表示“非法”(?),因為您試圖將基類分配給派生類。 假設d
已初始化,則只能以其他方式使用賦值運算符,例如b=d
。
您也可以編寫自己的賦值運算符來執行此操作,但我個人不建議這樣做。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.