简体   繁体   中英

assigning all properties of object of base class to derived one

I have derived class D from base class B, like follow:

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 . What is possible or best way to do it, if i have no rights to change B implementation? 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.

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. Undefined behaviour is as bad as it can get, as you don't have any guarantees what will happen next (crash, data corruptions, heisenbugs, ..).


About static_cast ptr conversions

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 . 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 ). Else, undefined behaviour. That is why there's dynamic_cast :

pd = dynamic_cast<D*>(pb);

Again, if *pb is of dynamic type D , everything is fine (same as above). But if *pb is not of dynamic type D , dynamic_cast returns a null pointer value ( nullptr ). This way, no undefined behaviour, you can check whether pd is nullptr now or not. 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 ).


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 ? You have to create an object of type D and copy or move the data from &b to this new object. This is what Subaru Tashiro's proposed in Method 2, like:

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.

There are other possibilities like wrapping the B -type object, but they differ from your approach of a derived class.


Some remarks why the conversion pd = &b is bad

  • First of all, Subaru Tashiro is right in that the data members that have been introduced with the D child class are problematic. If the D class adds any data members, how should C++ / the compiler know how they should be initialized? 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:

     |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 .

    • 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 ). 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. It's more probable that there's other meaningful data before (char*)pb and after (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).
    • 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).
  • If there's a vtable in D , it won't be initialized by the pointer conversion. Calling any virtual method of static_cast<D*>(pb) that has been introduced in the D class is therefore not a good idea.
  • RTTI might as well give you problems, since it can be implemented by storing the RTT info alongside the object. RTTI can be used in exceptions and dynamic_casts , so it's not only typeid that's affected.
  • The C++ Standard says, this conversion leads to undefined behaviour. 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. 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 .

Final remarks:

  • static_cast reference for this: 5.2.9/2, last 2 sentences
  • My answer differs from Subaru Tashiro's in that I'd "comdemn" the first method. 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, ....). 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.

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.

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. 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"

class B {
public:
  int bInt;
};

here we create a subclass that also has it's own variable called "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 . Note that at this point, *derivedObj is incomplete. To be specific, derivedObj->dInt has an undefined value.

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

Because D is derived from B, it also has the bInt variable. 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.

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.

int myNum = derivedObj->dInt;

If you plan to use static_cast, be sure to initialize members of the derived class. 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. 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:

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.

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.
  2. subclass B and write a constructor that takes a B and passes it to B's copy constructor.

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. You can only use the assignment operator the other way around such as b=d given that d is initialized.

You can also write your own assignment operator to do that but I personally don't recommend it.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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