10

As indicated in the title above, my question is simply whether or not a C++ cast does create a new object of the target class. Of course, I have used Google, MSDN, IBM and stackoverflow's search tool before asking this but I can't find an appropriate answer to my question.

Lets consider the following implementation of the diamond problem solved by using virtual inheritance:

#include <iostream>
#include <cstdlib>

struct A
{
  int a;

  A(): a(2) {  }
};

struct B: virtual public A
{
  int b;

  B(): b(7) {  }
};

struct C: virtual public A
{
  int c;

  C(): c(1) {  }
};

struct END: virtual public B, virtual public C
{
  int end;

  END(): end(8) {  }
};

int main()
{
  END *end = new END();
  A *a = dynamic_cast<A*>(end);
  B *b = dynamic_cast<B*>(end);
  C *c = dynamic_cast<C*>(end);

  std::cout << "Values of a:\na->a: " << a->a << "\n\n";
  std::cout << "Values of b:\nb->a: " << b->a << "\nb->b: " << b->b << "\n\n";
  std::cout << "Values of c:\nc->a: " << c->a << "\nc->c: " << c->c << "\n\n";

  std::cout << "Handle of end: " << end << "\n";
  std::cout << "Handle of a: " << a << "\n";
  std::cout << "Handle of b: " << b << "\n";
  std::cout << "Handle of c: " << c << "\n\n";
  system("PAUSE");
  return 0;
}

As I understood, the actual structure of B and C, which normally consists of both an embedded instance of A and variables of B resp. C, is destroyed since the virtual A of B and C is merged to one embedded object in END to avoid ambiguities. Since (as I always thought) dynamic_cast usually only increases the address stored by a pointer by the offset of the embedded (cast's) target class there will be a problem due to the fact that the target (B or C) class is divided into several parts.

But if I run the example with MSVC++ 2011 Express everything will happen as expected (i.e. it will run, all *.a output 2), the pointers only slightly differ. Therefor, I suspect that the casts nevertheless only move the addresses of the source pointers by the internal offset of B's / C's instance.

But how? How does the resulting instance of B / C know the position of the shared A object. Since there is only one A object inside the END object but normally an A object in B and C, either B or C must not have an instance of A, but, indeed, both seem to have an instance of it.

Or does virtual only delegate calls to A's members to a central A object without deleting the respective A objects of each base class which inherits virtual from A (i.e. does virtual actually not destroy the internal structure of inherited and therefor embedded objects but only not using their virtualized (= shared) members)?

Or does virtual create a new "offset map" (i.e. the map which tells the address offsets of all members relative to the pointer to a class instance, I dunno the actual term) for such casted objects to handle their "distributedness"?

I hope I have clarified everything, many thanks in advance
BlueBlobb

PS:
I'm sorry if there are some grammar mistakes, I'm only a beer loving Bavarian, not a native speaker :P

Edit:
If have added these lines to output the addresses of all int a's:

  std::cout << "Handle of end.a: " << &end->a << "\n";
  std::cout << "Handle of a.a: " << &a->a << "\n";
  std::cout << "Handle of a.b: " << &b->a << "\n";
  std::cout << "Handle of a.c: " << &c->a << "\n\n";

They are the same implying that there is indeed only one A object.

Seradir
  • 135
  • 1
  • 10
  • 1
    http://www.parashift.com/c++-faq/multiple-inheritance.html But the short answer is, "no." – Adam Burry Sep 07 '13 at 03:04
  • And note, you need `virtual public` for the B and C derivations from A, but *not* for the `END` derivation from B and C. A regular old, `struct END : public B, public C` would be sufficient. – WhozCraig Sep 07 '13 at 03:07
  • Suggest changing the title to match the actual question (which seems to be: "How does `dynamic_cast` "know" what to do in the case of virtual inheritance?" – M.M Aug 12 '18 at 08:53

4 Answers4

6

my question is simply whether or not a C++ cast does create a new object of the target class.

Yes, a cast to a class type would create new temporary object of that type.

Note that your example doesn't cast to a class anywhere: the only casts it performs are to pointer types. Those casts do create new instances of pointers - but not of the objects pointed to. I'm not sure what your example was supposed to demonstrate, nor how it is related to your stated question.

Also, dynamic_cast is unnecessary where you use it; an implicit conversion would work just as well.

Since (as I always thought) dynamic_cast usually only increases the address stored by a pointer by the offset of the embedded (cast's) target class

You must be thinking of static_cast or something. dynamic_cast is much more powerful. For example, it can cast from B* to C*, even though they are unrelated at compile time, by going down to END* and then back up the other branch. dynamic_cast utilizes run-time type information.

How does the resulting instance of B / C know the position of the shared A object.

This is implementation-dependent. A typical implementation would reserve space within the derived class instance to store an offset to its virtual base class instance. The constructor of the most-derived class initializes all those offsets.

Igor Tandetnik
  • 50,461
  • 4
  • 56
  • 85
  • Now I'm confused: Mark Ransom says that the standard of casts is that they won't create new objects and you say it'll create a new temporary object. – Seradir Sep 07 '13 at 03:31
  • 1
    In my humble opinion, @Mark Ransom is sadly mistaken in this case. But note again that your example doesn't match your question. You are asking about a cast to a "target class", but your example doesn't cast to a class type, but to a pointer type. That, of course, doesn't create a new instance of a class - it creates a new instance of a pointer. – Igor Tandetnik Sep 07 '13 at 03:33
  • If I understand you correctly, B *b = &((B)(*end)); will not be the same as the B object in end. – Seradir Sep 07 '13 at 03:42
  • This example won't compile: you can't take an address of an rvalue. Make it `const B& b = ((B)(*end));`. Yes, this creates a new instance of `B`, copy-constructed from `B` subobject of `*end`, and binds it to a reference. – Igor Tandetnik Sep 07 '13 at 03:53
  • I inserted printf("Copy constructor of B is called, this pointer: %p", this); in B's constructor and printf("Destructor of B is called."); in its destructor. Indeed, the constructor was called once and the destructor was never called. The this pointer and the end pointer are equal. But I assumed that you mean that the constructor will be called twice (with to different this addresses, the first should be identical to this of end) and the destructor once (when destroying the temp object). Wait, I test it ... – Seradir Sep 07 '13 at 03:55
  • At this point, with code snippets flying thick, it's hard for me to guess exactly what code you are running. If it's your original code, then it never creates any copies, so the result is expected (the reason the destructor is not called is because you are leaking your heap-allocated instance of `END`). – Igor Tandetnik Sep 07 '13 at 03:59
  • The reason was that I forgot to replace B *b = dynamic_cast(a); by B &b = ((B)(*end)); (as an addition, B *b = &((B)(*end)); indeed works). I corrected it and now the constructor is called once (when creating end) as well as the copy constructor and destructor of another object. You get the tick, 'cause you seem to be right. – Seradir Sep 07 '13 at 04:03
  • @BlueBlobb, despite my high reputation I do make mistakes. Once Igor pointed it out I made a quick correction. – Mark Ransom Sep 07 '13 at 04:08
  • You must be using Microsoft Visual C++. A standard-conforming compiler won't allow you to bind a temporary to a non-const reference; MSVC allows it as an extension (enabled by default, can be disabled with a compiler option). Just be aware that `B& b = ((B)(*end))` is non-standard; `const B& b = ((B)(*end))` is fine. – Igor Tandetnik Sep 07 '13 at 04:10
  • @Mark Ransom, everyone makes mistakes, so no problem. Igor's explanation sound logical to me. – Seradir Sep 07 '13 at 04:14
  • @Igor Tandetnik, yeah I use Microsoft Visual C++ (as seen in my question MSVC++ 2011 Express), but I wonder that there is an option to disable that. Normally, Microsoft isn't so polite ;). – Seradir Sep 07 '13 at 04:16
  • Thank you, from now on, I'll use it to be compatible to other standard-conforming compilers. – Seradir Sep 07 '13 at 04:23
5

No, you're just seeing the effects of multiple inheritance. In order for a pointer to be cast to a different base type, it has to be adjusted to the part of the object that represents that exact type. The compiler knows the original type of the pointer and the result type, so it can apply the necessary offsets. In order for the derived type to satisfy the "is-a" requirement it must have the necessary structure built in to emulate all of the base types.

There's one case where a cast can create a new object, and that's when you're casting to a type other than a pointer or reference type. Often that won't be possible unless you've defined a cast operator for that type.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • "Derived d; static_cast(d);" A new temporary instance of `Base` is too being created here, no overridden operators required. – Igor Tandetnik Sep 07 '13 at 03:27
  • What do you mean when saying _has to be adjusted_. According to my edit note, they all use the same A object (at least the same a), therefor only shifting the pointer when casting to the A part and still using the same "offset map" (or however this is called) as all other A objects can't work. Or do I understand the _has to be adjusted_ part or anything else wrong? – Seradir Sep 07 '13 at 03:28
0

The example you gave uses pointers.

A* a = dynamic_cast<A*>(end);

So the only "new" thing created here is another pointer, which will point to the "A" vtable of the object to which "end" points. It does not actually construct a new object of the class/struct types you are using.

Contrast with

A a;
B b(a);

Here a new object is created. But otherwise, casting does not create a new object of the destination cast type.

The reason the pointers differ is because they are pointing to the different vtables that preceed the data section of the underlying object.

Example:

#include <iostream>

using namespace std;

struct A {
    int a[64];
    A() { cout << "A()" << endl; }
    A(const A&) { cout << "A(A&)" << endl; }
    A& operator = (const A&) { cout << "A=A" << endl; return *this; }
};

struct B : virtual public A {
    int b[64];
    B() { cout << "B()" << endl; }
    B(const B&) { cout << "B(B&)" << endl; }
    B(const A&) { cout << "B(A&)" << endl; }
    B& operator = (const B&) { cout << "B=B" << endl; return *this; }
    B& operator = (const A&) { cout << "B=A" << endl; return *this; }
};

struct C : virtual public A {
    int c[64];
    C() { cout << "C()" << endl; }
    C(const C&) { cout << "C(C&)" << endl; }
    C(const B&) { cout << "C(B&)" << endl; }
    C(const A&) { cout << "C(A&)" << endl; }
    C& operator = (const C&) { cout << "C=C" << endl; return *this; }
    C& operator = (const B&) { cout << "C=B" << endl; return *this; }
    C& operator = (const A&) { cout << "C=A" << endl; return *this; }
};

struct END : virtual public B, C {
    int end[64];
    END() { cout << "END()" << endl; }
    END(const END&) { cout << "END(END&)" << endl; }
    END(const C&) { cout << "END(C&)" << endl; }
    END(const B&) { cout << "END(B&)" << endl; }
    END(const A&) { cout << "END(A&)" << endl; }
    END& operator = (const END&) { cout << "END=END" << endl; return *this; }
    END& operator = (const C&) { cout << "END=C" << endl; return *this; }
    END& operator = (const B&) { cout << "END=B" << endl; return *this; }
    END& operator = (const A&) { cout << "END=A" << endl; return *this; }
};

int main() {
    END* end = new END();

    A *a = dynamic_cast<A*>(end);
    B *b = dynamic_cast<B*>(end);
    C *c = dynamic_cast<C*>(end);

    std::cout << "end = " << (void*)end << std::endl;
    std::cout << "a = " << (void*)a << std::endl;
    std::cout << "b = " << (void*)b << std::endl;
    std::cout << "c = " << (void*)c << std::endl;

    // the direct pointers are going to have to differ
    // to point to the correct vtable. what about 'a' in all cases?
    std::cout << "end->a = " << (void*)&(end->a) << std::endl;
    std::cout << "a->a = " << (void*)&(a->a) << std::endl;
    std::cout << "b->a = " << (void*)&(b->a) << std::endl;
    std::cout << "c->a = " << (void*)&(c->a) << std::endl;


}

Which you can see running here: http://ideone.com/0QAoWE

kfsone
  • 23,617
  • 2
  • 42
  • 74
  • Does too, unless the destination type is a reference. You said yourself that casting to a pointer type creates a new object of that type. – Igor Tandetnik Sep 07 '13 at 04:04
  • Pointers are objects, as, being pedantic, you appear to acknowledge: "a new object is created - a new pointer to type X". – Igor Tandetnik Sep 07 '13 at 04:14
  • I was trying to be humorous. – kfsone Sep 07 '13 at 04:18
  • There, humor removed, and as you can see from the idone output, they all point to the same object. – kfsone Sep 07 '13 at 04:22
  • I must admit that, to my eternal shame, I'm not getting the joke. Sorry for being dense. In any case, in all seriousness, pointers are, indeed, objects, and casting to a pointer does, indeed, create a new object of pointer type. I guess that, when you say "object", what you really mean is "an object of class type". – Igor Tandetnik Sep 07 '13 at 04:25
  • To me an "object" is an instance of a class/struct with members/functors. A pointer is a simple variable. It does not have a vtable. It occupies 4 or 8 bytes depending on environment, and can be held in a register or on the stack. Your question appears to be asking if casting from A* to B* will create a new "B" to which "B* b" can point. It does not. No constructors are called. It will take the address of the source object's B* vtable and store that into a new variable. Whether this occupies additional stack or a register will be context dependent, and you'd have to look at assembly. – kfsone Sep 07 '13 at 04:29
  • 1
    To the C++ standard, an "object" is something different: 1.8p1 The constructs in a C++ program create, destroy, refer to, access, and manipulate objects. An *object* is a region of storage. – Igor Tandetnik Sep 07 '13 at 04:33
  • Yeah, that's right. As Igor Tandetnik already pointed out, according to my actual question, I rather should have used cast's between classes, my mistake. But, indeed, independent on whether a cast (built-in) operator is called on a simple type or a class, new memory will be allocated and a constructor may be called if there is one (i.e. if the target type is a class). – Seradir Sep 07 '13 at 04:36
  • @BlueBlobb I'd taken your original use of the term "object" to mean a separate instance of one of the four class types (A, B, C, End) rather than just another pointer. See the IDEOne example above, or look at the assembly. No new constructor is called. There is only one class-instance (the one allocated with new); only one constructor call. Looking at "g++ -O3 -ggdb" assembly under Linux, the compiler only calculates the vtable address when it needs to pass it as an argument to the iostream functions. – kfsone Sep 07 '13 at 04:49
  • @IgorTandetnik The humor was simply that he was *not* asking "Does assigning a value to a new variable create a new variable", he was asking if a temporary of one of his class-instances was being constructed thru casting. The intent was to move away from the use of the term "object" since he was clearly not using the C++ standard definition of it. And in the examples he gave, no new complex objects were being constructed. – kfsone Sep 07 '13 at 04:53
  • I understand. Instead of the standard definition (any heap or stack region is an object) you used my definition (object is equal to instance) to be not confusing to me. Actually, when creating my example, my main aim was to create a situation in which new instances are created, but I totally failed ;) – Seradir Sep 07 '13 at 05:00
0

At least with MSVC in VS 2017, the answer is a definite maybe.

// Value is a struct that contains a member: std::string _string;
// _value is a std::variant<> containing a Value as one member

template <> std::string const &Get<std::string>() const
{
    // Required pre-condition: _value.index() == TYPE_VALUE
    Value const &value = std::get<TYPE_VALUE>(_value);
    return static_cast<std::string>(value._string);
}

std::string const &test()
{
    static std::string x = "hello world";
    return static_cast<std::string>(x);
}

Get() is a very small snippet from a much larger project, and won't operate without the support of several hundred other lines of code. test() is something I quickly threw together to investigate.

As written, Get()generates the following warning:

warning C4172: returning address of local variable or temporary

while test() compiles clean. If I remove the static_cast<> from Get(), it also compiles cleanly.

P.S. in hindsight, I ought to rename _value to something like _payload, since it can contain a lot more than a Value.

dgnuff
  • 3,195
  • 2
  • 18
  • 32
  • This is nothing to do with the code in the question. (Maybe you attempt to answer the title literally, but I think it is clear from reading the question that OP chose a poor title) – M.M Aug 12 '18 at 08:52
  • @M.M And maybe a poor first sentence ... That said, I'll leave it up to the community as to whether this should be kept, since I came here dealing with the issue addressed in my answer, and this page was one of the first links that Google provided. :/ – dgnuff Aug 12 '18 at 09:08