簡體   English   中英

將基類對象的所有屬性分配給派生對象

[英]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 ++不允許從&bpd的轉換,既不能隱式( pd = &b )也不能顯式( pd = static_cast<D*>(&b) ),而不會導致未定義的行為。 未定義的行為非常糟糕,因為您無法保證接下來會發生什么(崩潰,數據損壞,heisenbugs等)。


關於static_cast ptr轉換

讓我們從標准中引入一些名稱,以使事情更清楚:

B* pb = &d;

然后將靜態類型*pbB動態類型*pbD 靜態類型基本上是編譯器看到的,而動態類型則是運行時實際存在的類型。

以下轉換很好:

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不好

  • 首先,Subaru Tashiro是正確的,因為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)任何虛擬方法不是一個好主意。
  • RTTI可能還會給您帶來問題,因為可以通過將RTT信息與對象一起存儲來實現它。 RTTI可以用於exceptions和dynamic_casts ,因此受影響的不僅是typeid
  • C ++標准說,這種轉換導致不確定的行為。 也就是說,即使您可以將*pdB子對象與某些沒有問題的編譯器/ C ++實現一起使用,也無法保證它將與所有編譯器/版本一起使用。 以上幾點是我想到的一些問題,但我絕不是專家。 本質上, 無法預測/無法保證執行此轉換並使用結果pdB子對象時會發生什么

最后說明:

  • 為此的static_cast參考:5.2.9 / 2,最后2個句子
  • 我的回答與Subaru Tashiro的不同之處在於我會“譴責”第一種方法。 您可以將其寫下來,並且它是一個格式正確的程序(這意味着它應該編譯),但是您無法預測會發生什么,並且它是一個hack(可能有用,但別處重復,風格不好,...)。 )。 如果您真的想這樣做,我建議您使用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值也與*baseObjbInt ,因此它們是相等的。

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);

因此,總而言之,您有兩種選擇:

  1. 子類B並使用static_cast初始化D。確保初始化D的成員變量。
  2. 子類B並編寫一個接受B的構造函數,並將其傳遞給B的副本構造函數。

這兩種方法都具有相同的結果,不同之處在於程序在后台如何執行此操作。

不幸的是,在您的示例中,使用d=b表示“非法”(?),因為您試圖將基類分配給派生類。 假設d已初始化,則只能以其他方式使用賦值運算符,例如b=d

您也可以編寫自己的賦值運算符來執行此操作,但我個人不建議這樣做。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM