473,382 Members | 1,441 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,382 software developers and data experts.

shared_ptr and incomplete types

I have trouble understanding incomplete types and their interplay with
shared_ptr. Consider the following code, composed of two source files
and one header:

//------------------------------------------------------------------
// test.h
#ifndef TEST_H_
#define TEST_H_
#include <boost/shared_ptr.hpp>
using namespace boost;

struct A;
struct B
{
shared_ptr<Ap;
B();
~B();
};
#endif
//------------------------------------------------------------------
// test1.cpp
#include "test.h"
#include <iostream>
using namespace std;

struct A {
~A();
};
A::~A() { cout <<"Destruct A" <<endl; }

B::B() : p(new A) {}
//------------------------------------------------------------------
// test2.cpp
using namespace std;
using namespace boost;

#include "test.h"

B::~B() { }

int main()
{
B b;
return 0;
}
//------------------------------------------------------------------
From my understanding the above should not compile. A is incomplete in
test2.cpp. Instantiation of shared_ptr<Adestructor should happen
during compilation of B's destructor. Since B's destructor is declared
in test2.cpp, the instantiation of shared_ptr<Adestructor should
cause checked_delete to be applied to incomplete type A and fail.
However, it doesn't and this compiles and runs correctly. However,
adding two lines

A *a;
shared_ptr<Ap(a);

to main() in test.cpp generates a compile error, which of course it
should. I am confused. Could anyone clarify this for me please?
Thanks.
Nov 28 '07 #1
5 4238
mi***********@gmail.com wrote:
During constructor invocation, a pointer to that function is stored
somewhere inside shared_ptr<A>
That's one thing which I find worrying about shared_ptr. While it's
nice that you can safely create shared_ptrs of incomplete types, the
price for this is that shared_ptr becomes quite bulky. If I'm not
completely mistaken, a shared_ptr object has the size of 3 pointers plus
it allocates dynamically memory for an integral type, which means that
it additionally uses memory required by the integral type plus any
ancillary memory required by a dynamically-allocated object required by
the memory management and possibly memory alignment. This means that one
single "pointer" could, depending on the system, require something like
64 bytes or even more. (Compare this to a system where you have a smart
pointer which uses a reference counter in the object itself, and
requires complete types: Only 1 pointer is needed in the smart pointer
object, and adding the size of the reference counter to it, the total
memory requirement in a typical 32-bit system is 8 bytes.)

This can become a major issue if you are, for example, creating an
array of millions of shared_ptrs. Just the shared_ptrs themselves could
easily require more memory than the objects they are point to, if the
objects are small. Since shared_ptr hides this issue very well, a
typical programmer might not be aware of this.

And better not copy shared_ptrs in a tight inner loop which requires
extreme speed...
Nov 28 '07 #2
On 2007-11-28 09:00:23 -0500, Juha Nieminen <no****@thanks.invalidsaid:
mi***********@gmail.com wrote:
>During constructor invocation, a pointer to that function is stored
somewhere inside shared_ptr<A>

That's one thing which I find worrying about shared_ptr. While it's
nice that you can safely create shared_ptrs of incomplete types, the
price for this is that shared_ptr becomes quite bulky. If I'm not
completely mistaken, a shared_ptr object has the size of 3 pointers plus
it allocates dynamically memory for an integral type
It's typically two pointers. One holds the A* that the shared_ptr<A>
deals with, and the other holds a pointer to an allocated block that
contains the pointer that was passed to the constructor, the reference
count, and the deleter.
, which means that
it additionally uses memory required by the integral type plus any
ancillary memory required by a dynamically-allocated object required by
the memory management and possibly memory alignment. This means that one
single "pointer" could, depending on the system, require something like
64 bytes or even more. (Compare this to a system where you have a smart
pointer which uses a reference counter in the object itself, and
requires complete types: Only 1 pointer is needed in the smart pointer
object, and adding the size of the reference counter to it, the total
memory requirement in a typical 32-bit system is 8 bytes.)

This can become a major issue if you are, for example, creating an
array of millions of shared_ptrs. Just the shared_ptrs themselves could
easily require more memory than the objects they are point to, if the
objects are small. Since shared_ptr hides this issue very well, a
typical programmer might not be aware of this.
If so, then a "typical" programmer is incompetent.
>
And better not copy shared_ptrs in a tight inner loop which requires
extreme speed...
Copying shared_ptr objects is fast and cheap. Not as cheap as copying a
naked pointer, but if you need the semantics of a shared_ptr, the cost
is not prohibitive. Two pointer copies and an integer increment.

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)

Nov 28 '07 #3
On Nov 28, 3:00 pm, Juha Nieminen <nos...@thanks.invalidwrote:
mike.polya...@gmail.com wrote:
During constructor invocation, a pointer to that function is stored
somewhere inside shared_ptr<A>
That's one thing which I find worrying about shared_ptr. While it's
nice that you can safely create shared_ptrs of incomplete types, the
price for this is that shared_ptr becomes quite bulky. If I'm not
completely mistaken, a shared_ptr object has the size of 3 pointers plus
it allocates dynamically memory for an integral type, which means that
it additionally uses memory required by the integral type plus any
ancillary memory required by a dynamically-allocated object required by
the memory management and possibly memory alignment.
It's typically only two pointers, I think. I can't think of any
reason why there would be a third.

There are two basic philosophies with regards to reference
counted pointers: invasive, and non-invasive. Invasive
reference counted pointers are only a single pointer, require no
extra allocations, and are significantly safer, in that you can
create a new reference counted pointer from a raw pointer at any
time, even if other reference counted pointers already exist.
(Because they are smaller, they are also slightly faster, but
this is rarely an issue.) On the other hand, they are invasive;
the object being pointed to must know about them (typically be
deriving from a common base class---virtually, if the hierarchy
is open). Which means no reference counted pointers to existing
classes, nor to non-class types. For a "standard" pointer,
that's pretty much a killer exclusion.
This means that one single "pointer" could, depending on the
system, require something like 64 bytes or even more. (Compare
this to a system where you have a smart pointer which uses a
reference counter in the object itself, and requires complete
types: Only 1 pointer is needed in the smart pointer object,
and adding the size of the reference counter to it, the total
memory requirement in a typical 32-bit system is 8 bytes.)
That doesn't match up with my measurements. On my 32-bits
systems, alignment considerations mean that the counter itself
ends up requiring 8 bytes, so my invasive reference counted
pointers require a total of n*4+8 bytes for each object (where n
is the number of pointers to that object). I think
boost::shared_ptr requires more, but the one time I implemented
non-invasive reference counting, I used a custom allocator for
the ints, with the result that the pointers required n*8+4
bytes. Not a big difference, unless, of course, you have large
arrays of reference counted pointers.

A much more important consideration is safety. Consider a
simplified example:

T* p = new T ;
Ptr< T p1( p ) ;
Ptr< T p2( p ) ;

With a non-invasive pointer, such as boost::shared_ptr, this
breaks, resulting the memory being freed prematurely. With
invasive pointers, it works.
This can become a major issue if you are, for example, creating an
array of millions of shared_ptrs.
Yes, but is there ever any reason to have a container of
shared_ptr?
Just the shared_ptrs themselves could
easily require more memory than the objects they are point to, if the
objects are small. Since shared_ptr hides this issue very well, a
typical programmer might not be aware of this.
That's even true of the invasive pointers. I'd guess that most
of the time I'm using reference counted pointers, it's for
"agent" classes, with no data members (except for the implicit
vptr).
And better not copy shared_ptrs in a tight inner loop which requires
extreme speed...
I don't think that the difference will normally be that great.

The one case there might be a significant difference is if the
compiler will return (and pass) class types which fit in a
single register in registers. An invasive pointer will
typically fit in a single register, a non-invasive one won't.
But while this optimization has often been discussed, I don't
know of a single compiler which will do it (unless the class
type is a POD, but no reference counted pointer will be a POD).
So it's rather accademic.

--
James Kanze (GABI Software) email:ja*********@gmail.com
Conseils en informatique orientée objet/
Beratung in objektorientierter Datenverarbeitung
9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
Nov 29 '07 #4
On 2007-11-29 05:16:00 -0500, James Kanze <ja*********@gmail.comsaid:
>
That doesn't match up with my measurements. On my 32-bits
systems, alignment considerations mean that the counter itself
ends up requiring 8 bytes, so my invasive reference counted
pointers require a total of n*4+8 bytes for each object (where n
is the number of pointers to that object). I think
boost::shared_ptr requires more,
It does, because it does more. std::tr1::shared_ptr<Ttraffics in
T*'s, but the control block holds a copy of the pointer that was passed
to the constructor, which can point to an object of a type derived from
T. It also holds an optional deleter, whose type and, therefore, size
is up to the user.

--
Pete
Roundhouse Consulting, Ltd. (www.versatilecoding.com) Author of "The
Standard C++ Library Extensions: a Tutorial and Reference
(www.petebecker.com/tr1book)

Nov 29 '07 #5
James Kanze wrote:
On the other hand, they are invasive;
the object being pointed to must know about them (typically be
deriving from a common base class---virtually, if the hierarchy
is open). Which means no reference counted pointers to existing
classes, nor to non-class types. For a "standard" pointer,
that's pretty much a killer exclusion.
I wonder if the language couldn't be enhanced so that you can allocate
existing objects and non-class types in such a way that the compiler
will internally make the allocation larger by sizeof reference counter
and then a special internal shared pointer can be used to manage objects
of this type.

Perhaps something like:

shared SomeClass* ptr = shared_new SomeClass();

That 'ptr' would be of the same size as a regular pointer. The memory
amount allocated by 'shared_new' would be sizeof(size_t) (or whatever)
larger than the memory allocated by the equivalent 'new'.

The semantics could perhaps be so that these would be erroneous:

shared SomeClass* ptr = new SomeClass(); // error
SomeClass* ptr = shared_new SomeClass(); // error

shared SomeClass* ptr = shared_new SomeClass();
shared SomeClass* ptr2 = ptr; // Ok
SomeClass* ptr3 = ptr; // error

SomeClass* ptr = new SomeClass();
shared SomeClass* ptr2 = ptr; // error
> This can become a major issue if you are, for example, creating an
array of millions of shared_ptrs.

Yes, but is there ever any reason to have a container of
shared_ptr?
For example if you want a vector containing different types of objects
(which all have been derived from a common base class) and want the
memory taken by those objects be managed.
Nov 29 '07 #6

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

Similar topics

4
by: Philippe Guglielmetti | last post by:
I just ported old (VC6) working code to VC7.1 and have trouble with something like: class A; // forward typedef boost::smart_ptr<A> Aptr; class B{ Aptr a; virtual ~B(); // implemented...
7
by: Andrew Ward | last post by:
Hi All, Considering the following code: struct A; struct B { std::list<A> l; };
5
by: ctick | last post by:
Are there any advantages of using boost::shared_ptr other than auto_ptr from standard library?
6
by: Ryan Mitchley | last post by:
Hi all Given bool bResult; shared_ptr<cSampleData> pNewData; shared_ptr<cBase> pNewBase; where cSampleData is descended from cBase, the following gives me a valid pNewData to the correct...
2
by: adebaene | last post by:
Hello group, There seems to be a bug int the interop layer in VC2005 when dealing with certain pointer types (or values?) Here is a repro case using Boost version 1.32 and C++/CLI : using...
1
by: Alan Johnson | last post by:
From the standard 5.3.5/5: "If the object being deleted has incomplete class type at the point of deletion and the complete class has a non-trivial destructor or a deallocation function, the...
2
by: dave_dp | last post by:
Hi folks, I'm interested as to what extent using incomplete types doesn't result in a undefined behavior, more generally it touches the usage of incomplete types.. for example, it is stated that...
3
by: Tim H | last post by:
I'm newish to C++ but not to C. I'm confused by this code. test1() is fine. test2() fails to compile. /tmp/inherit_ptr.cpp: In function âvoid test2()â: /tmp/inherit_ptr.cpp:52: error: no...
6
by: hsmit.home | last post by:
Hello, I came across a strange error and it's really been bugging me. Maybe someone else has come across this and any insight would be appreciated. What I'm trying to accomplish is using...
1
by: CloudSolutions | last post by:
Introduction: For many beginners and individual users, requiring a credit card and email registration may pose a barrier when starting to use cloud servers. However, some cloud server providers now...
0
isladogs
by: isladogs | last post by:
The next Access Europe User Group meeting will be on Wednesday 3 Apr 2024 starting at 18:00 UK time (6PM UTC+1) and finishing by 19:30 (7.30PM). In this session, we are pleased to welcome former...
0
by: taylorcarr | last post by:
A Canon printer is a smart device known for being advanced, efficient, and reliable. It is designed for home, office, and hybrid workspace use and can also be used for a variety of purposes. However,...
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: ryjfgjl | last post by:
In our work, we often receive Excel tables with data in the same format. If we want to analyze these data, it can be difficult to analyze them because the data is spread across multiple Excel files...
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
0
BarryA
by: BarryA | last post by:
What are the essential steps and strategies outlined in the Data Structures and Algorithms (DSA) roadmap for aspiring data scientists? How can individuals effectively utilize this roadmap to progress...
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...

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.