繁体   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