简体   繁体   English

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

[英]assigning all properties of object of base class to derived one

I have derived class D from base class B, like follow: 我从基类B派生了类D,如下所示:

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

I want to process received pointer B* b , in a way that declare D* d , and initialize *d with all same property values from *b . 欲处理接收到的指针B* b ,在声明的方式D* d ,以及初始化*d与从所有相同的属性值*b What is possible or best way to do it, if i have no rights to change B implementation? 如果我无权更改B实现,有什么可能或最好的方法? Something like 就像是

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

Thanks 谢谢

Basic answer 基本答案

Notation: 符号:

B b;
B* pb = &b;
D* pd;

If you have the pointer pb and want to have a pointer pd , where *pd has the same (values of) data members as *pb , then you have to copy or move the data from *pb to some (memory) location and let pd point to that location. 如果您有指针pb并希望有一个指针pd ,其中*pd具有与*pb相同的(值)数据成员,则必须将数据从*pb复制或移动到某个(内存)位置并让pd指向该位置。

C++ does not allow a conversion from &b to pd neither implicit ( pd = &b ) nor explicit ( pd = static_cast<D*>(&b) ) without resulting in undefined behaviour. C ++不允许从&bpd的转换,既不能隐式( pd = &b )也不能显式( pd = static_cast<D*>(&b) ),而不会导致未定义的行为。 Undefined behaviour is as bad as it can get, as you don't have any guarantees what will happen next (crash, data corruptions, heisenbugs, ..). 未定义的行为非常糟糕,因为您无法保证接下来会发生什么(崩溃,数据损坏,heisenbugs等)。


About static_cast ptr conversions 关于static_cast ptr转换

Let's introduce some naming from the Standard to make things more clear: 让我们从标准中引入一些名称,以使事情更清楚:

B* pb = &d;

Then the static type of *pb is B whereas the dynamic type of *pb is D . 然后将静态类型*pbB动态类型*pbD Static type is basically what the compiler sees, and dynamic type is what is actually in there at run time. 静态类型基本上是编译器看到的,而动态类型则是运行时实际存在的类型。

The following conversion is fine: 以下转换很好:

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`

The last line is fine in case *pb has the dynamic type D (or a derived type of D ). 最后一行是在壳体细*pb具有动态型D (或派生类型的D )。 Else, undefined behaviour. 其他,不确定的行为。 That is why there's dynamic_cast : 这就是为什么要使用dynamic_cast

pd = dynamic_cast<D*>(pb);

Again, if *pb is of dynamic type D , everything is fine (same as above). 同样,如果*pb是动态类型D ,则一切都很好(与上面相同)。 But if *pb is not of dynamic type D , dynamic_cast returns a null pointer value ( nullptr ). 但是,如果*pb不是动态类型D ,则dynamic_cast返回空指针值( nullptr )。 This way, no undefined behaviour, you can check whether pd is nullptr now or not. 这样,没有未定义的行为,您可以检查pd现在是否为nullptr As dynamic_cast is substantially slower than static_cast , if you really know that the conversion will succeed, you can use static_cast instead ( see the Qt example ). 由于dynamic_cast的速度实际上比static_cast慢,因此如果您真的知道转换将成功,则可以改用static_cast请参见Qt示例 )。


Copy or move the data to a new object 将数据复制或移动到新对象

Now what if you want to have a pointer pd such that all data members ("properties") are the same as the data members of &b ? 现在,如果您希望拥有一个指针pd ,以使所有数据成员(“属性”)都与&b的数据成员相同,该怎么办? You have to create an object of type D and copy or move the data from &b to this new object. 您必须创建一个D类型的对象,并将数据从&b复制或移动到该新对象。 This is what Subaru Tashiro's proposed in Method 2, like: 这是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;

Note both the copy and move line create a new D -type object to store the copied/moved data in. You don't need to provide both copy and move support. 请注意,复制行和移动行都创建了一个新的D型对象,用于存储复制/移动的数据。您无需同时提供复制和移动支持。

There are other possibilities like wrapping the B -type object, but they differ from your approach of a derived class. 还有其他可能性,例如包装B型对象,但它们与派生类的方法不同。


Some remarks why the conversion pd = &b is bad 一些说明为什么转换pd = &b不好

  • First of all, Subaru Tashiro is right in that the data members that have been introduced with the D child class are problematic. 首先,Subaru Tashiro是正确的,因为D子类引入的数据成员是有问题的。 If the D class adds any data members, how should C++ / the compiler know how they should be initialized? 如果D类添加了任何数据成员,那么C ++ /编译器应如何知道应如何初始化它们? It cannot just leave them uninitialized as this would be error prone (consider class invariants). 它不能仅仅让它们未初始化,因为这很容易出错(考虑类不变式)。
  • Memory problems. 内存问题。 Example: Imagine a D -type object would look like this in memory: 示例:想象一个D型对象在内存中看起来像这样:

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

    Now, when you do pd = static_cast<D*>(pb) , the address contained in pb would be interpreted as start of B sub-object and be decreased by 2 bytes to get the start of D object . 现在,当您执行pd = static_cast<D*>(pb) ,包含在pb的地址将被解释为B子对象的开始,并减少2个字节以获取D object开始

    • As you have only allocated the space for the b object, it's only guaranteed you can access the memory from (char*)pb to (char*)pb + sizeof(b) (w/o considering alignment inside b ). 由于仅为b对象分配了空间,因此只能保证可以访问(char*)pb(char*)pb + sizeof(b)的内存(char*)pb考虑b内的对齐方式)。 It could be possible that accessing the memory before (char*)pb or after (char*)pb + sizeof(b) leads to an error, eg the CPU complaining about you accessing virtual memory that has not been mapped to physical memory. (char*)pb或之后(char*)pb + sizeof(b)之后访问内存可能会导致错误,例如,CPU抱怨您正在访问尚未映射到物理内存的虚拟内存。 It's more probable that there's other meaningful data before (char*)pb and after (char*)pb + sizeof(b) . 更有可能在(char*)pb和之后(char*)pb + sizeof(b)之后还有其他有意义的数据。 By writing to any data member of static_cast<D*>(pb) that has been introduced in the D class, you can corrupt this other data (not sure about the other data members). 通过写入D类中引入的static_cast<D*>(pb)任何数据成员,您可以破坏此其他数据(不确定其他数据成员)。
    • You can get alignment issues when accessing any members via *pd , eg if your compiler assumes all D-type objects start at a 2-byte boundary and all B-type objects start at a 4-byte boundary (tricky and pathologic, but a possible problem). 通过*pd访问任何成员时,您可能会遇到对齐问题,例如,如果编译器假定所有D类对象都以2字节为边界开始,而所有B类对象都以4字节为边界开始(易碎和病理性,但可能的问题)。
  • If there's a vtable in D , it won't be initialized by the pointer conversion. 如果D有一个vtable,则不会通过指针转换对其进行初始化。 Calling any virtual method of static_cast<D*>(pb) that has been introduced in the D class is therefore not a good idea. 因此,调用已在D类中引入的static_cast<D*>(pb)任何虚拟方法不是一个好主意。
  • RTTI might as well give you problems, since it can be implemented by storing the RTT info alongside the object. RTTI可能还会给您带来问题,因为可以通过将RTT信息与对象一起存储来实现它。 RTTI can be used in exceptions and dynamic_casts , so it's not only typeid that's affected. RTTI可以用于exceptions和dynamic_casts ,因此受影响的不仅是typeid
  • The C++ Standard says, this conversion leads to undefined behaviour. C ++标准说,这种转换导致不确定的行为。 That is, even if you can use the B sub-object of *pd with some compilers / C++ implementations w/o problems, there's no guarantee it will work with all compilers / versions. 也就是说,即使您可以将*pdB子对象与某些没有问题的编译器/ C ++实现一起使用,也无法保证它将与所有编译器/版本一起使用。 The points above are some of the problems that came to my mind, but I'm by no means an expert. 以上几点是我想到的一些问题,但我绝不是专家。 Essentially, there's no way to predict / no guarantee what will happen when you perform this conversion and use the B sub-object of the resulting pd . 本质上, 无法预测/无法保证执行此转换并使用结果pdB子对象时会发生什么

Final remarks: 最后说明:

  • static_cast reference for this: 5.2.9/2, last 2 sentences 为此的static_cast参考:5.2.9 / 2,最后2个句子
  • My answer differs from Subaru Tashiro's in that I'd "comdemn" the first method. 我的回答与Subaru Tashiro的不同之处在于我会“谴责”第一种方法。 You can write it down and it's a well-formed program (that means, it should compile), but you cannot predict what will happen and it's a hack (may work, but not to be repeated elsewhere, bad style, ....). 您可以将其写下来,并且它是一个格式正确的程序(这意味着它应该编译),但是您无法预测会发生什么,并且它是一个hack(可能有用,但别处重复,风格不好,...)。 )。 If you really want to do this, I'd rather suggest using a reinterpret_cast and a lot of comments around this to indicate you know what you're doing right there and that it is a hack. 如果您真的想这样做,我建议您使用reinterpret_cast和很多注释,以表明您知道自己在做什么,这确实是黑客。

Method 1: You can use static_cast to use a base class to initialize a derived class but this will result in an incomplete derived object. 方法1:可以使用static_cast使用基类初始化派生类,但这将导致派生对象不完整。

source 资源

static_cast can perform conversions between pointers to related classes, not only from the derived class to its base, but also from a base class to its derived. static_cast不仅可以在指向相关类的指针之间执行转换,不仅可以从派生类到其基类,还可以从基类到其派生类。 This ensures that at least the classes are compatible if the proper object is converted, but no safety check is performed during runtime to check if the object being converted is in fact a full object of the destination type. 这样可以确保如果转换了正确的对象,至少这些类是兼容的,但是在运行时不会执行安全检查来检查转换的对象是否实际上是目标类型的完整对象。 Therefore, it is up to the programmer to ensure that the conversion is safe. 因此,程序员必须确保转换是安全的。

See this example: 请参阅以下示例:

here we write the base class that has a public variable called "bInt" 在这里,我们编写一个具有称为“ bInt”的公共变量的基类

class B {
public:
  int bInt;
};

here we create a subclass that also has it's own variable called "dInt" 在这里,我们创建一个子类,该子类也具有自己的变量“ dInt”

class D: public B {
public:
  int dInt;
};

here we have a new instance of the base class. 在这里,我们有了基类的新实例。 We initialize the base object's variable. 我们初始化基础对象的变量。

B * baseObj = new B;
baseObj->bInt = 1;

Here we use the base class to declare and initialize the derived class by using static_cast . 在这里,我们使用基类通过static_cast声明和初始化派生类。 Note that at this point, *derivedObj is incomplete. 请注意,此时*derivedObj不完整。 To be specific, derivedObj->dInt has an undefined value. 具体来说, derivedObj->dInt具有未定义的值。

D * derivedObj = static_cast<D*>(baseObj);

Because D is derived from B, it also has the bInt variable. 由于D是从B派生的,因此它还具有bInt变量。 And because we used static_cast to initialize *derivedObj , the value of it's bInt is also the same as *baseObj 's bInt , therefore they are equal. 并且由于我们使用static_cast来初始化*derivedObj ,因此它的bInt值也与*baseObjbInt ,因此它们是相等的。

if(baseObj->bInt == derivedObj->bInt)
{
  display("it's equal");
}

But because the base class has no dInt variable, this variable will be left uninitialized and the outcome of this procedure is undefined. 但是,由于基类没有dInt变量,因此该变量将保持未初始化状态,并且该过程的结果不确定。

int myNum = derivedObj->dInt;

If you plan to use static_cast, be sure to initialize members of the derived class. 如果计划使用static_cast,请确保初始化派生类的成员。 It's a good habit to do so anyway. 无论如何,这是一个好习惯。

When using static_cast, you don't need to know all of the members of B. D automatically has all of B's member values. 使用static_cast时,您不需要知道B的所有成员。D自动具有B的所有成员值。 But what you're trying to do is dangerous because you are creating an incomplete object. 但是,您尝试做的事情很危险,因为您正在创建不完整的对象。

Method 2: If you really need to subclass B, then write D's constructor as one that takes a B* and initializes it's B by copying like so: 方法2:如果确实需要继承B,则将D的构造函数编写为采用B *并通过复制将其初始化为B的构造函数,如下所示:

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

So when you want to declare and initialize an object of type D this is how you do it. 因此,当您要声明和初始化D类型的对象时,这就是您的操作方式。

B *baseObj = new B;
D *derivedObj = new D(*baseObj);

So in summary, you have two choices: 因此,总而言之,您有两种选择:

  1. subclass B and use static_cast to initialize D. Make sure to initialize D's member variables. 子类B并使用static_cast初始化D。确保初始化D的成员变量。
  2. subclass B and write a constructor that takes a B and passes it to B's copy constructor. 子类B并编写一个接受B的构造函数,并将其传递给B的副本构造函数。

Both of these methods have the same outcome, the difference is how the program does this behind the scenes. 这两种方法都具有相同的结果,不同之处在于程序在后台如何执行此操作。

Unfortunately in your example, using d=b is 'illegal' (?) because you are trying to assign a base class to a derived class. 不幸的是,在您的示例中,使用d=b表示“非法”(?),因为您试图将基类分配给派生类。 You can only use the assignment operator the other way around such as b=d given that d is initialized. 假设d已初始化,则只能以其他方式使用赋值运算符,例如b=d

You can also write your own assignment operator to do that but I personally don't recommend it. 您也可以编写自己的赋值运算符来执行此操作,但我个人不建议这样做。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM