将基类对象的所有属性分配给派生类

时间:2013-03-26 17:22:57

标签: c++

我从基类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);

由于

2 个答案:

答案 0 :(得分:1)

基本答案

<强>符号:

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;

然后*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_caststatic_cast慢得多,如果确实知道转换成功,则可以使用static_cast代替(see the Qt example)。


将数据复制或移动到新对象

现在,如果您希望指针pd使所有数据成员(&#34;属性&#34;)与&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个字节以获得< em> D对象的开始。

    • 由于您只为b对象分配了空间,因此只能保证您可以从(char*)pb(char*)pb + sizeof(b)访问内存(不考虑内部对齐) b)。有可能在(char*)pb之前或(char*)pb + sizeof(b)之后访问内存会导致错误,例如CPU抱怨您访问尚未映射到物理内存的虚拟内存。在(char*)pb之前和(char*)pb + sizeof(b)之后,其他有意义的数据更有可能发生。通过写入static_cast<D*>(pb)类中引入的D的任何数据成员,您可以破坏其他数据(不确定其他数据成员)。
    • 通过*pd访问任何成员时,您可能会遇到对齐问题,例如如果您的编译器假设所有D类型对象都以2字节边界开始,并且所有B类型对象都以4字节边界开始(棘手和病态,但可能存在问题)。
  • 如果D中存在vtable,则不会通过指针转换对其进行初始化。因此,调用static_cast<D*>(pb)类中引入的D的任何虚拟方法都不是一个好主意。
  • RTTI也可能会给您带来问题,因为它可以通过将RTT信息与对象一起存储来实现。 RTTI可用于例外和dynamic_casts,因此不仅typeid受到影响。
  • C ++标准说,这种转换会导致未定义的行为。也就是说,即使您可以使用B的{​​{1}}子对象与某些编译器/ C ++实现没有问题,也不能保证它适用于所有编译器/版本。以上几点是我脑海中出现的一些问题,但我绝不是专家。从本质上讲,无法预测/无法保证执行此转换时会发生什么,并使用生成的*pd B子对象。< / LI>

最后的评论:

  • pd参考:5.2.9 / 2,最后2句
  • 我的回答与Subaru Tashiro的不同之处在于,我是&#34; comdemn&#34;第一种方法。你可以把它写下来,它是一个结构良好的程序(也就是说,它应该编译),但是你无法预测会发生什么,它是一个黑客攻击(可能有效,但不能在其他地方重复,不好风格,....)。如果您真的想要这样做,我建议您使用static_cast和大量的评论来表明您知道自己在那里做了什么,而且这是一个黑客攻击。< / LI>

答案 1 :(得分:0)

方法1: 您可以使用static_cast来使用基类来初始化派生类,但这会导致派生对象不完整。

source

  

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的{​​{1}}相同,因此它们是相等的。< / p>

bInt

但是因为基类没有if(baseObj->bInt == derivedObj->bInt) { display("it's equal"); } 变量,所以此变量将保持未初始化状态,并且此过程的结果未定义。

dInt

如果您打算使用static_cast,请确保初始化派生类的成员。无论如何,这是一个好习惯。

使用static_cast时,您不需要知道B. D的所有成员自动拥有B的所有成员值。但是你要做的事情很危险,因为你正在创造一个不完整的对象。

方法2:如果你真的需要继承B,那么把D的构造函数写成一个带有B *的构造函数,并通过这样复制来初始化它的B:

int myNum = derivedObj->dInt;

因此,当您想要声明并初始化D类型的对象时,就是这样做的。

D(const B &pass) : B(pass)

总而言之,您有两种选择:

  1. 子类B并使用static_cast初始化D.确保初始化D的成员变量。
  2. 子类B并编写一个构造函数,它接受一个B并将其传递给B的复制构造函数。
  3. 这两种方法都有相同的结果,区别在于程序是如何在幕后完成的。

    不幸的是,在您的示例中,使用B *baseObj = new B; D *derivedObj = new D(*baseObj); 是“非法”(?),因为您尝试将基类分配给派生类。在d=b被初始化的情况下,您只能使用赋值运算符,例如b=d

    您也可以编写自己的赋值运算符,但我个人不推荐它。