0

我从基类 B 派生了类 D,如下所示:

class D : public B
{
//staff or nothing
}

我想以B* b声明的方式处理接收到的指针, 并 使用所有相同的属性值进行D* d初始化。如果我无权更改实施,有什么可能或最好的方法?就像是*d*bB

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

谢谢

4

2 回答 2

1

基本答案

符号:

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_castptr 转换

让我们从标准中引入一些命名以使事情更清楚:

B* pb = &d;

那么静态类型*pbB动态*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。Asdynamic_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-type 对象,但它们与派生类的方法不同。


一些说明为什么转换pd = &b不好

  • 首先,田代昴是对的,在D子类中引入的数据成员是有问题的。如果D该类添加了任何数据成员,C++ / 编译器应该如何知道它们应该如何初始化?它不能只是让它们未初始化,因为这很容易出错(考虑类不变量)。
  • 内存问题。示例:想象一个D-type 对象在内存中看起来像这样:

    |DD[BBBB]DDDDDDDD|
        ^start of B sub-object
     ^start of D object
    

    现在,当你这样做时pd = static_cast<D*>(pb),包含的地址pb将被解释为B 子对象的开始,并减少 2 个字节以获得D 对象的开始

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

最后的评论:

  • static_cast参考:5.2.9/2,最后两句
  • 我的回答与 Subaru Tashiro 的不同之处在于我会“谴责”第一种方法。你可以把它写下来,它是一个格式良好的程序(这意味着它应该编译),但你无法预测会发生什么,它是一个 hack(可能有效,但不要在其他地方重复,糟糕的风格,...... )。如果你真的想这样做,我宁愿建议使用 areinterpret_cast和很多评论来表明你知道你在做什么,并且这是一个 hack。
于 2013-03-28T19:03:47.623 回答
0

方法一: 可以使用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's一样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);

所以总而言之,你有两个选择:

  1. 子类 B 并使用 static_cast 初始化 D。确保初始化 D 的成员变量。
  2. 子类 B 并编写一个构造函数,该构造函数接受 B 并将其传递给 B 的复制构造函数。

这两种方法具有相同的结果,不同之处在于程序如何在幕后执行此操作。

不幸的是,在您的示例中,使用d=b是“非法的”(?),因为您试图将基类分配给派生类。您只能以其他方式使用赋值运算符,例如已初始化的b=d给定。d

您也可以编写自己的赋值运算符来执行此操作,但我个人不建议这样做。

于 2013-03-26T18:34:29.630 回答