Why virtual function can't be unimplemented when allocated with 'new'?
Asked Answered
T

5

8
struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // ok
  obj.bar();  // <-- added this edition
  A* pm = (A*)malloc(sizeof(A)); // ok
  A* pn = new A; // linker error
}

For objects on stack it works fine. But for allocation on heap with new (not malloc), it gives linker error:

undefined reference to `vtable for A'
Tatiania answered 3/6, 2011 at 17:12 Comment(12)
I'm shocked! If it's allocated on the stack, it's OK? O.OPike
Stack allocation doesn't work, either ("A obj;"), are you sure this is "// ok"?Apple
What is your compiler/linker?Elviselvish
This code is fake: it will not compile without an explicit cast on the result of malloc.Rejoin
I think it's important to state compiler optimization flags. With -O4, gcc compiles "A obj;" but not "new A"; with -O0 both fail.Apple
@AndreyT: You keep referring to posts and code as "fake" as if someone is trying to pull something over on you. There's a difference between "fake" code and "bad" code.Woodpecker
@John: AndreyT means that the posted code is not the same as the code that caused the OP's problem. This often happens, and it can be very irritating. For instance, here the code simply doesn't compile (according to AndreyT - I haven't tried it myself). Which renders the question pointless.Caldeira
@John: It's in contrast to "the actual code". An opposite of actual is fake.Propolis
@Kiril, @AndreyT, @All, this code compiles fine with A obj; ideone.com/AYYleTatiania
@GMan: Strictly speaking, the opposite of "actual" is not "fake". However, whilst I wouldn't use the same wording, I agree that the given code is "fake"; that is, it's not the code that the OP is actually using.Grogshop
@Tomalak @GMan, I have just missed cast before malloc() how can it be a fake code just for that ? I have edited it though.Tatiania
@Tomalak: I said "An opposite" for a reason, as in "some may consider this an opposite", not the opposite. :) (I'm not prescriptive, so telling me what words ought to be used rarely has an influence. As long as the intention can be made known without dishonesty, I care not.)Propolis
R
6

Firstly, this code is not compilable, since in C++ void * cannot be implicitly converted to A *. An explicit cast is required.

Secondly, the example with malloc is completely irrelevant. malloc allocates raw memory, with has absolutely no relation to any specific types. In this case malloc knows noting about any A and it does not create an object of type A.

For this reasons the real example for this question should look as follows

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // ok
  A* pn = new A; // linker error
}

And the question is why the first declaration produces no liker error while the second one does.

From the formal point of view, your program is invalid because it violates formal requirements of C++ language (specifically ODR). In practice, both declarations could or should produce the same error, since in both cases the object formally requires a pointer to VMT. In this case VMT cannot be created, since some functions are undefined. However, the first declaration simply slips through just because the compiler was able to optimize-out all references to VMT for the first declaration (and not for the second). It is also quite possible that the compiler was able to optimize-out the whole obj object, since it is not referenced anywhere else.

In GCC (since you appear to be using GCC) it is easy to trigger the same error for the first declaration as well

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj; // linker error
  A *p = &obj;
  p->bar(); 
}

The above code will produce the same linker error in GCC even though the undefined function foo is still not used in this code.

In other words, it is simply a matter of adding sufficient amount of code to make the compiler believe that the object's VMT is needed. The difference in behavior between the declarations has nothing to do with C++ language in this case. It is just an implementation issue specific to your compiler.

Rejoin answered 3/6, 2011 at 17:41 Comment(5)
You meant obj.bar() right? BTW, +1 for a nice explanation for a complicated matter.Elviselvish
@Chris A.: I meant p->bar(). Thank you for pointing it out. Fixed.Rejoin
it seems that linker error is compiler dependant. Following doesn't give error for A obj; ideone.com/aYf5WTatiania
and NO my code is NOT FAKE (as you mentioned in comments to question). There are many polite words available for "wrong".Tatiania
@iammilind: There's no doubt that it is compiler dependent. From the language point of view your code is invalid. After that, you are at the mercy of the compiler. And that is always compiler-dependent. What will trigger the error and what will not might not be easily predictable. The example you made passes compilation because it does not rely on VMT (the compiler called the function directly instead). There will always be some explanation why it compiles (when it compiles), but it is a waste of time anyway. The code is invalid.Rejoin
H
9

Because malloc does not call (or attempt to call in this case) A's constructor, whereas new does.

This code compiles and notes where linker errors occur with GCC:

#include <cstdlib>

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // linker error
  A* pm = (A*) malloc(sizeof(A)); // ok
  A* pn = new A; // linker error
}
Hypoglycemia answered 3/6, 2011 at 17:13 Comment(16)
@Neil Butterworth - Neil you are back ? meta.stackexchange.com/questions/61077/…Handmedown
Stack allocation also calls the constructor, so that doesn't explain why the "A obj;" should work but "new A" should not.Apple
Not that I disagree with you, but just trying to understand. Doesn't A obj; also call A's constructor? So this alone can't be the reason, right? (Happy to be shown the error in my thinking.)Elviselvish
thanks for the edit. So, at least on gcc, A obj; does produce a linker error. OP, can you verify or state your compiler/linker?Elviselvish
@NB: thanks for the edit. Compiler optimization matters, I think, with -O4 gcc complies "A obj;".Apple
The question is obviously not about malloc. That line with malloc shouldn't even be there (not even mentioning that it is not compilable). The question is about the difference between new-ed object and locally declared object.Rejoin
@Chris A.: codepad.org uses GCC and it doesn't produce linker error for A obj. Of course, it could be a matter of optimization settings.Rejoin
@Cris A. The program is ill-formed with and without the call to new. The standard requires that all used functions are defined in the program §3.2/3 Every program shall contain exactly one definition of every non-inline function or object that is used in that program; no diagnostic required. . For virtual functions, the definition of used is §3.2/2 A virtual member function is used if it is not pure.. The fact that your compiler did not complain when the variable was allocated in the stack falls in the no diagnostics required bit of the rule. Maybe this should be an answer...Frans
So to summarize, it seems that some linkers are causing an error on A obj; (Mine does and Neil's does) and some aren't (OP and AndreyT) due to optimization, and that this is a part of the standard that is up for interpretation? If obj is used, then it definitely will cause a linker error.Elviselvish
@Chris At high optimisation levels, the code will produce no errors because it will all be optimised away, as the A instances are never actually used for anything. At those levels, main becomes an empty function.Hypoglycemia
@David: No. The function can be proven to never be used in the "on the stack" case as the object cannot be used polymorphically in that case.Grogshop
What's to check, @Iammilind? The code there is pretty much the same as the code here, and you've already been told what's wrong with the code here. The code has problems, but the compiler is not required to alert you to them or to reject your program because of them. Behavior in that situation is undefined. The compiler can generate whatever program it wants to, including one that behaves the way you hoped it would.Laocoon
@Rob, no the code is not exactly same. 1st it doesn't cause linker error for A obj; and 2nd obj is not unused (to let compiler optimize it away). Still question remains the same.Tatiania
@iammilind: You need to learn to extrapolate information. The reasons you're getting the output you are have been fully explained in these answers, we can't hold your hand through every test case or example. Whether or not you get a linker error on a certain compiler on certain settings isn't relevant, in the C++ language the code is wrong.Propolis
@GMan, why so rude ? I am not saying to explain me everything. I have got a proper answer in @AndreyT's. Though @Neil's is also goodTatiania
@Tomalak Geret'kal: The standard has a precise definition of used for a virtual function (§3.2/2), and that program uses by the standard definition of use that function. The standard is clear and precise: that code is ill-formed. Read the comment above. Besides that fact (i.e. the standard says so), it can be proven that the virtual functions is actually used, if for nothing else, to store its pointer in the virtual table (hand waving: or whatever dynamic dispatch mechanism your compiler uses, i.e. virtual table). That the object is in the stack does not free it from having a vtable.Frans
R
6

Firstly, this code is not compilable, since in C++ void * cannot be implicitly converted to A *. An explicit cast is required.

Secondly, the example with malloc is completely irrelevant. malloc allocates raw memory, with has absolutely no relation to any specific types. In this case malloc knows noting about any A and it does not create an object of type A.

For this reasons the real example for this question should look as follows

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj;        // ok
  A* pn = new A; // linker error
}

And the question is why the first declaration produces no liker error while the second one does.

From the formal point of view, your program is invalid because it violates formal requirements of C++ language (specifically ODR). In practice, both declarations could or should produce the same error, since in both cases the object formally requires a pointer to VMT. In this case VMT cannot be created, since some functions are undefined. However, the first declaration simply slips through just because the compiler was able to optimize-out all references to VMT for the first declaration (and not for the second). It is also quite possible that the compiler was able to optimize-out the whole obj object, since it is not referenced anywhere else.

In GCC (since you appear to be using GCC) it is easy to trigger the same error for the first declaration as well

struct A
{
  virtual void foo();  // unused and unimplemented
  virtual void bar () {}
};

int main ()
{
  A obj; // linker error
  A *p = &obj;
  p->bar(); 
}

The above code will produce the same linker error in GCC even though the undefined function foo is still not used in this code.

In other words, it is simply a matter of adding sufficient amount of code to make the compiler believe that the object's VMT is needed. The difference in behavior between the declarations has nothing to do with C++ language in this case. It is just an implementation issue specific to your compiler.

Rejoin answered 3/6, 2011 at 17:41 Comment(5)
You meant obj.bar() right? BTW, +1 for a nice explanation for a complicated matter.Elviselvish
@Chris A.: I meant p->bar(). Thank you for pointing it out. Fixed.Rejoin
it seems that linker error is compiler dependant. Following doesn't give error for A obj; ideone.com/aYf5WTatiania
and NO my code is NOT FAKE (as you mentioned in comments to question). There are many polite words available for "wrong".Tatiania
@iammilind: There's no doubt that it is compiler dependent. From the language point of view your code is invalid. After that, you are at the mercy of the compiler. And that is always compiler-dependent. What will trigger the error and what will not might not be easily predictable. The example you made passes compilation because it does not rely on VMT (the compiler called the function directly instead). There will always be some explanation why it compiles (when it compiles), but it is a waste of time anyway. The code is invalid.Rejoin
G
3

You cannot leave a virtual function unimplemented, even if it's 'unused' (because it's in fact used by the vtable). This is the bug in the code.

The bug manifests itself in this particular fashion because of a peculiar implementation of vtables in the compiler. You have left unimplemented the first virtual function. The compiler inserts the vtable whenever it sees the implementation of the first virtual function of the class. Since there isn't any, there's no vtable.

If you leave the second function unimplemented, the linker will complain about that specific function, not about the vtable.

[edit] Your compiler probably optimized-out a copy of A on the stack, that's the reason the linker didn't complain.

The malloc line doesn't actually reference an object of type A, that's why it doesn't create a linker problem. There's another problem with this line though: it should not compile. malloc returns void* which does not convert to other types of pointer without a cast.

Golden answered 3/6, 2011 at 17:23 Comment(0)
S
1

The standard requires exactly one implementation of A::foo if A is instantiated anywhere in the program. Regardless of whether the instantiation is through the declaration of a local variable or through a new expression. However, no diagnostic is required if this rule is broken; if you provide no declaration, or if you provide two or more, it's simply undefined behavior. Anything the compiler does is "correct". In this case, what it probably happening is:

  • the reason the definition is required is because it is referenced in the vtable,
  • the constructor of A is inline, so the code which initializes the vptr (and triggers the instantiation of the vtable) is fully visible to the compiler,

  • since all uses of the object are visible to the compiler, it can see that the vptr is never used, so it simply suppresses it.

  • and with no vptr, no vtable need be generated, so there is no reference to the virtual function.

In sum, it depends on how the compiler optimizes; you might get an error for both the local declaration and the new expression, or for neither, or for one and not the other. And it might depend on the optimization options, or whatever. As far as C++ is concerned, it might depend on the phases of the moon, and instead of an error, you might simply get code which crashed when you ran it (but the scenarios I stated first are the most likely).

Suffrage answered 3/6, 2011 at 18:26 Comment(0)
G
0

Unusedness is irrelevant. Define all virtual functions. It's as simple as that.

Your automatic storage duration object (what you've chosen to call an object "on the stack") is not used [polymorphically], so you get no diagnostic. That doesn't make it right.

Grogshop answered 4/6, 2011 at 2:3 Comment(3)
I have some use cases for making the virtual method unimplemented and that's why this came to me. The problem is why language doesn't allow it.Tatiania
Un-used-ness from the perspective of the compiler is relevant. The program is ill formed, and an standard compliant compiler does not need to diagnose (in either case!). Also the original code does not have any static storage duration object, but rather automatic storage. To be static the static keyword would have to be used (or the variable would have to be at namespace level)Frans
@David: Oops, I meant "automatic". And I think you've read my "irrelevant" backwards: it's certainly relevant to the compiler, as you say. That is, it's irrelevant when considering exceptions to the rule. As I said, define all virtual functions.Grogshop

© 2022 - 2024 — McMap. All rights reserved.