我从基类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);
由于
答案 0 :(得分: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_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
代替(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
不好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
的任何虚拟方法都不是一个好主意。dynamic_casts
,因此不仅typeid
受到影响。B
的{{1}}子对象与某些编译器/ C ++实现没有问题,也不能保证它适用于所有编译器/版本。以上几点是我脑海中出现的一些问题,但我绝不是专家。从本质上讲,无法预测/无法保证执行此转换时会发生什么,并使用生成的*pd
的B
子对象。< / LI>
最后的评论:
pd
参考:5.2.9 / 2,最后2句static_cast
和大量的评论来表明您知道自己在那里做了什么,而且这是一个黑客攻击。< / LI>
答案 1 :(得分:0)
方法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
的{{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)
总而言之,您有两种选择:
这两种方法都有相同的结果,区别在于程序是如何在幕后完成的。
不幸的是,在您的示例中,使用B *baseObj = new B;
D *derivedObj = new D(*baseObj);
是“非法”(?),因为您尝试将基类分配给派生类。在d=b
被初始化的情况下,您只能使用赋值运算符,例如b=d
。
您也可以编写自己的赋值运算符,但我个人不推荐它。