473,406 Members | 2,619 Online
Bytes | Software Development & Data Engineering Community
Post Job

Home Posts Topics Members FAQ

Join Bytes to post your question to a community of 473,406 software developers and data experts.

[Q] Virtual destructors and linking

Hello,

I'm rather new to the advanced topics, therefore I cannot explain the
following myself. Could anyone give me a hint on this one?

I'm trying to avoid link-time dependencies on (a test version of)
certain classes. In other words I don't want to link stuff that I don't
use. One thing that worked for me was replacing instance members
(MyClass myClass) within a class with auto_ptr (auto_ptr<MyClass>
myClass) and initializing it with a new instance of a class in the
product code and a NULL in the test code.

This works... unless the class being auto_ptr'ed has a non-virtual
destructor. The linker is happily eating input with virtual
destructors. Otherwise the implementation of the destructor is claimed
to be missing.

The whole thing was originally tested on M$ visual studio compiler,
then on gcc (cygwin/3.4.4).

Is this a feature of compiler(s) being too smart (even when all
optimizations are disabled) or is it an expected behavior? What exactly
is happening here? If this is compiler-specific I assume it is not very
scalable and I'd better drop using it. Any other suggestions in that
case?

Here is some sample code:

#include <memory>
#include <iostream>

class MyClass2
{
public:
MyClass2() {};
virtual ~MyClass2();
};

int main()
{
// Uncommenting the following line would lead to complaint
// from linker that requires implementation for ~MyClass2().
// std::auto_ptr<MyClass2> myClass2r (new MyClass2);

std::auto_ptr<MyClass2> myClass2n;
}

p.s. I'm working on a rather big legacy code thing and therefore a bit
limited in redesign. Link-time stubbing for test purposes seems to be
the easiest approach.

Thanks in advance, Oleksii.

Jul 23 '05 #1
7 2180
Oleksii wrote:
Hello,

I'm rather new to the advanced topics, therefore I cannot explain the
following myself. Could anyone give me a hint on this one?


You're lucky it works even without a virtual destructor.
You effectively use the destructor in either case.

Jul 23 '05 #2
Oleksii wrote:
I'm rather new to the advanced topics, therefore I cannot explain the
following myself. Could anyone give me a hint on this one?

I'm trying to avoid link-time dependencies on (a test version of)
certain classes. In other words I don't want to link stuff that I don't
use. One thing that worked for me was replacing instance members
(MyClass myClass) within a class with auto_ptr (auto_ptr<MyClass>
myClass) and initializing it with a new instance of a class in the
product code and a NULL in the test code.
But that's definitely not the same... An instance of a class requires
different code to call members than an auto_ptr...
This works... unless the class being auto_ptr'ed has a non-virtual
destructor. The linker is happily eating input with virtual
destructors. Otherwise the implementation of the destructor is claimed
to be missing.

The whole thing was originally tested on M$ visual studio compiler,
then on gcc (cygwin/3.4.4).

Is this a feature of compiler(s) being too smart (even when all
optimizations are disabled) or is it an expected behavior? What exactly
is happening here? If this is compiler-specific I assume it is not very
scalable and I'd better drop using it. Any other suggestions in that
case?

Here is some sample code:

#include <memory>
#include <iostream>

class MyClass2
{
public:
MyClass2() {};
virtual ~MyClass2();
So, why don't you just write

virtual ~MyClass2() {}

The same empty parentheses don't seem to cause you any trouble in the
[unneeded] default constructor...
};

int main()
{
// Uncommenting the following line would lead to complaint
// from linker that requires implementation for ~MyClass2().
// std::auto_ptr<MyClass2> myClass2r (new MyClass2);
Of course it requires the implementation of ~MyClass2() -- it uses it!
The destructor for 'auto_ptr' actually destroys the object by calling
'delete' on the point it "owns".
std::auto_ptr<MyClass2> myClass2n;
I am not sure how the compiler could be that smart, but probably it can.

Here you never actually create an instance of 'MyClass', so it never needs
to destroy it. I am guessing that the clever implementation of
std::auto_ptr somehow knows whether the object has or hasn't been
constructed and produces a special destructor in the case where only the
default constructor was used. Optimizing compiler might be able to do
something like that...
}

p.s. I'm working on a rather big legacy code thing and therefore a bit
limited in redesign. Link-time stubbing for test purposes seems to be
the easiest approach.


What exactly are your limitations? Can't you give your classes empty
(doing nothing) destructors?

V
Jul 23 '05 #3
First of all you're right, in the implementation I have to change all
myClass.DoSomehting() into myClass->DoSomething(), but I consider it as
a minor change. I assume that was the difference you have mentioned. I
don't have to add anything though - no additional work for destructors,
since smart pointers do everything under the hood.

Regarding my limitations. I cannot change design of the classes, but I
can tweak classes a bit (e.g. replacing instances with pointers). The
problem with changing design is that the code is not testable
standalone - it is a huge monolithic piece of sw.
(Ideally) I don't want to link/target all libraries I use in my
production code, but only the necessary ones (at the moment I have to
copy 18(!) DLLs to the test folder to be able to instantiate my class
in test environment...). When I've used auto_ptr's I only have to
include the definitions of the classes, but I don't have to link/target
the implementations (DLLs) along!

That is also the reason why I don't provide any empty implementations
for destructors - they do have they full-blown implementations in a
separate DLL, but I don't want to link/stub it because in principle it
is never used.

The reason why I come up with the question is that I don't understand
how is it possible that this works :) and how scalable it is
(compiler-specific).

I hope this makes it clearer.

oleksii

Jul 23 '05 #4
Ron Natalie wrote:
I'm rather new to the advanced topics, therefore I cannot explain the
following myself. Could anyone give me a hint on this one?


You're lucky it works even without a virtual destructor.
You effectively use the destructor in either case.


Yep, I also feel that. And therefore before using it in the production
code I want to understand why is it working and not throwing any
exceptions (e.g. when auto_ptr is trying to determine which destructor
has to be called). What I think now is that might be a smart
implementation of the delete operator (not the operator delete if I'm
correct), that does not bind to the class implementation since NULL
pointer is provided. Just a thought.

I've looked through the auto_ptr implementation and I don't see
anything very special about this case.

Nevertheless the question remains: why this damn thing works with
virtual constructors and complains when there is no virtual
constructor...

oleksii

Jul 23 '05 #5
Oleksii wrote:
[..]
The reason why I come up with the question is that I don't understand
how is it possible that this works :) and how scalable it is
(compiler-specific).


I am not sure, but I have a gut feeling that it's compiler-specific.
You should take a look at the implementation of the 'auto_ptr' class
template. Perhaps the compiler/library employs some kind of indirect
construction and since the other (underlying) template is not used
(and therefore not instantiated) in the case when you only use the
default c-tor for 'auto_ptr', the code to call the destructor is not
there (not instantiated), and the linker doesn't need it. Now, why
the things are different between virtual/non-virtual destructors, I
don't know. Hard to imagine how it would be implemented. Then again,
I am not a compiler expert, perhaps there is something that your
compiler does behind the scenes, which it cannot do if the destructor
is non-virtual...

V
Jul 23 '05 #6
Oleksii wrote:
Is this a feature of compiler(s) being too smart (even when all
optimizations are disabled) or is it an expected behavior? What exactly
is happening here? If this is compiler-specific I assume it is not very
scalable and I'd better drop using it. Any other suggestions in that
case?

Here is some sample code:

#include <memory>
#include <iostream>

class MyClass2
{
public:
MyClass2() {};
virtual ~MyClass2();
};

int main()
{
// Uncommenting the following line would lead to complaint
// from linker that requires implementation for ~MyClass2().
// std::auto_ptr<MyClass2> myClass2r (new MyClass2);

std::auto_ptr<MyClass2> myClass2n;
}

p.s. I'm working on a rather big legacy code thing and therefore a bit
limited in redesign. Link-time stubbing for test purposes seems to be
the easiest approach.


Hi! First I want to say that I'm 99% sure that the standard does not
in any way force a system to link this without crying about the missing
"virtual ~MyClass2". But I can tell you why it works since it's really
very basic.

Let's say we have a pointer called "p" which is of type "MyClass2*".
If "MyClass2" has a non-virtual dtor, then "delete p;" will directly
invoke "MyClass2::~MyClass2()". Whether or not "p" is NULL is only
known at runtime, and so the linker will cry about
"MyClass2::~MyClass2()" if it can't be found.

If "MyClass2" has a virtual dtor, then "delete p;" will call the dtor
of "MyClass2" or the dtor of a class derived from "MyClass2" if "p"
happens to point to such a derived class. Now, that could be done by
many different means of reflection, but the way it is usually done is
by using a virtual-function-table (short: vtable). For this to work
the compiler places a pointer to that vtable inside every instance of
"MyClass2". Now to call a virtual function on "MyClass2" the compiler
looks up the vtable by using that vtable pointer stored somewhere
"inside MyClass2", and then look up the function-address in the vtable.
Then it just calls the function at that address, whatever it might be.
So in short, the "delete p" thingy does no longer have to know the
address of "MyClass2::~MyClass2()". If "p" points to a "MyClass2"
the dtor slot in the vtable will point to "MyClass2::~MyClass2()",
but if "p" points to some derived class that slot will point to
dtor of that derived class.
Now, the compiler only has to know the address of the vtable when
initializing a new instance of "MyClass2" since this is the point where
it will store the vtable's address "inside" the "MyClass2" instance.
So, to sum it up: the ctor code of "MyClass2" will take the address of
"MyClass2"'s vtable, and the vtable will reference the dtor. Since the
ctor of "MyClass2" is not called by your program the linker will not
link it. And since the ctor is the only part that directly references
the vtable of "MyClass2" the vtable will also not be linked, and since
the vtable of "MyClass2" is the only part that directly references the
dtor of "MyClass2" the dtor will also not be linked.

So it's not about a compiler being too smart, but about a linker that
does it's job as it's supposed to do it.

Look at this for example:

class MyClass2
{
public:
MyClass2() {};

// with ~MyClass2 being virtual the call to MyClass2::MyClass2
// in main() will cause a linker error, since the compiler has
// to create the vtable for MyClass2 which contains a reference
// to ~MyClass2. If you try the same with a non-virtual ~MyClass2
// the program will probably link fine (at least with MSVC7.1
// it definately does) since the dtor is never being called,
// and since it's not virtual anymore it's also not being
// referenced.
virtual ~MyClass2();
};

int main()
{
int space[100];
reinterpret_cast<MyClass2*>(&space)->MyClass2::MyClass2();
}
Jul 23 '05 #7
Thanks a lot for your post! What I missed myself is the part that the
object was never actually created (constructor is not linked at all). I
only have focused on the destruction totally ignoring the construction
part.

Now things became clear. I assume any well-written linker should behave
in this manner, otherwise a lot of unused classes would be linked
together, which is unwanted behavior.

Thanks!

oleksii

Jul 23 '05 #8

This thread has been closed and replies have been disabled. Please start a new discussion.

Similar topics

2
by: Chunhui Han | last post by:
Hi, I was recently reading about virtual base classes in C++. The book I was reading says that it is illegal to have non-virtual destructor for the virtual base class. It seems to me that...
7
by: qazmlp | last post by:
When a member function is declared as virtual in the base class, the derived class versions of it are always treated as virtual. I am just wondering, why the same concept was not used for the...
11
by: Bonj | last post by:
Hello, Can anyone help me with these fairly simple questions. 1) What is the point in virtual destructors - I've heard it's a good thing for base-classes, but what are the advantages and...
5
by: Ruben Campos | last post by:
I've recently noticed that it's not allowed to call a pure (non-implemented) virtual method inside a constructor or a destructor, doesn't matter if this method is declared in the considered class...
11
by: santosh | last post by:
Hello, I was going through the Marshal Cline's C++ FAQ-Lite. I have a doubt regarding section 33.10. Here he is declaring a pure virtual destructor in the base class. And again defining...
4
by: Zeng | last post by:
I often run into situation where I would like to have a method such as that has derived behavior similar to destructors in c++, is that possible? public BaseClass { private int m_dataInBase;...
5
by: druberego | last post by:
I read google and tried to find the solution myself. YES I do know that you can get undefined references if you: a) forget to implement the code for a prototype/header file item, or b) you forget...
9
by: desktop | last post by:
On this page: http://www.eptacom.net/pubblicazioni/pub_eng/mdisp.html Shape specify the virtual function: virtual double Intersect( const Shape& s) = 0; then the derived class Circle...
1
by: Stephen Horne | last post by:
On Fri, 10 Oct 2008 13:42:57 -0700 (PDT), James Kanze <james.kanze@gmail.comwrote: There are issues with some compilers, but probably only old ones. I remember problems with Borland C++...
0
by: Charles Arthur | last post by:
How do i turn on java script on a villaon, callus and itel keypad mobile phone
0
by: emmanuelkatto | last post by:
Hi All, I am Emmanuel katto from Uganda. I want to ask what challenges you've faced while migrating a website to cloud. Please let me know. Thanks! Emmanuel
1
by: nemocccc | last post by:
hello, everyone, I want to develop a software for my android phone for daily needs, any suggestions?
0
by: Hystou | last post by:
There are some requirements for setting up RAID: 1. The motherboard and BIOS support RAID configuration. 2. The motherboard has 2 or more available SATA protocol SSD/HDD slots (including MSATA, M.2...
0
by: Hystou | last post by:
Most computers default to English, but sometimes we require a different language, especially when relocating. Forgot to request a specific language before your computer shipped? No problem! You can...
0
Oralloy
by: Oralloy | last post by:
Hello folks, I am unable to find appropriate documentation on the type promotion of bit-fields when using the generalised comparison operator "<=>". The problem is that using the GNU compilers,...
0
jinu1996
by: jinu1996 | last post by:
In today's digital age, having a compelling online presence is paramount for businesses aiming to thrive in a competitive landscape. At the heart of this digital strategy lies an intricately woven...
0
by: Hystou | last post by:
Overview: Windows 11 and 10 have less user interface control over operating system update behaviour than previous versions of Windows. In Windows 11 and 10, there is no way to turn off the Windows...
0
tracyyun
by: tracyyun | last post by:
Dear forum friends, With the development of smart home technology, a variety of wireless communication protocols have appeared on the market, such as Zigbee, Z-Wave, Wi-Fi, Bluetooth, etc. Each...

By using Bytes.com and it's services, you agree to our Privacy Policy and Terms of Use.

To disable or enable advertisements and analytics tracking please visit the manage ads & tracking page.