If the responsibility for cleaning up dynamically allocated stuff shifts between your wrapper class and the C library based on how things get used, you are either dealing with a badly designed C library, or you are trying to do too much in your wrapper class.
In the first case, all you can do is keep track of who is responsible for the cleanup and hope no mistakes are made (either by you or by the maintainers of the C library).
In the second case, you should rethink your wrapper design. Does all the functionality belong together in the same class, or can it be split into multiple classes. Perhaps the C library uses something similar to the Facade design pattern and you should keep a similar structure in your C++ wrapper.
In any case, even if the C library is responsible for cleaning up some stuff, there is nothing wrong with keeping a reference/pointer to that stuff. You just need to remember that you are not responsible for cleaning up what the pointer refers to.
The point is that this
is an implicit formal parameter (containing the address of the object whose method you are calling). It is not a local variable.
Look at the generated code of your program. I compiled (on Linux/Debian/Sid/x86-64 with GCC 4.9.1) your example arman.cc
with
gcc -O1 -fverbose-asm -S arman.cc
and got the function main
below
.globl main
.type main, @function
main:
.LFB512:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
.cfi_lsda 0x3,.LLSDA512
pushq %rbx #
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
subq $48, %rsp #,
.cfi_def_cfa_offset 64
movq $0, 16(%rsp) #, MEM[(struct _Vector_impl *)&vec]._M_start
movq $0, 24(%rsp) #, MEM[(struct _Vector_impl *)&vec]._M_finish
movq $0, 32(%rsp) #, MEM[(struct _Vector_impl *)&vec]._M_end_of_storage
movq $0, (%rsp) #, MEM[(struct A *)&__x]
movq %rsp, %rcx #,
movl $100, %edx #,
movl $0, %esi #,
leaq 16(%rsp), %rdi #, tmp92
.LEHB0:
call _ZNSt6vectorI1ASaIS0_EE14_M_fill_insertEN9__gnu_cxx17__normal_iteratorIPS0_S2_EEmRKS0_ #
.LEHE0:
movq 16(%rsp), %rdi # MEM[(struct _Vector_base *)&vec]._M_impl._M_start, D.10014
testq %rdi, %rdi # D.10014
je .L34 #,
call _ZdlPv #
jmp .L34 #
.L33:
movq %rax, %rbx #, tmp91
movq 16(%rsp), %rdi # MEM[(struct _Vector_base *)&vec]._M_impl._M_start, D.10014
testq %rdi, %rdi # D.10014
je .L32 #,
call _ZdlPv #
.L32:
movq %rbx, %rdi # tmp91,
.LEHB1:
call _Unwind_Resume #
.LEHE1:
.L34:
movl $0, %eax #,
addq $48, %rsp #,
.cfi_def_cfa_offset 16
popq %rbx #
.cfi_def_cfa_offset 8
ret
.cfi_endproc
You see that some space for your vec
is allocated on the stack (e.g. with subq $48, %rsp
etc...) and then the address of that zone on the stack is passed as the this
formal argument (using the usual x86-64 ABI conventions, which dictates (p 20) that the first argument of function is passed thru register %rdi
) so you could say that this
is, at the beginning of some member function, in the register %rdi
...
IIRC, the wording of the C++ standard are vague enough to permit the this
argument to be passed in a special way, but all the ABIs I heard of are passing it exactly as the first (pointer) argument of usual C functions.
BTW, you should trust the compiler and let it pass this
as convenient and as prescribed by ABI specifications.
Best Answer
unique_ptr<T, D>
is actually specially designed to be able to work with more arbitrary handle-like types. I spelled out the template name fully becauseD
is the key here. Normallyunique_ptr<T, D>::get()
returns aT*
. That is the default, but this can be overridden byD
: the deleter type.If the deleter type has a
pointer
alias (D::pointer
is legal syntax), thenunique_ptr<T, D>::get()
will return that type. This allows you to have something likeunique_ptr<GLuint, gl::program_deleter>
, wheregl::program_deleter::pointer
is of typeint
.I bring all of this up because
shared_ptr
cannot do this.unique_ptr<T, D>
gets away with it because the deleter is actually part of theunique_ptr
type itself. By contrast, whileshared_ptr
's constructors can take a deleter, the only thing that deleter function can do is delete the memory.So
shared_ptr<GLuint>::get()
will always return aGLuint*
. This means that, if you want to useshared_ptr
as some kind of shared handle type, that type must be dynamically allocated in some way. You may not be using the global heap, but it cannot just store and return integer either.shared_ptr<T>
always contains aT*
.So no matter what, you're going to have to manage
GLuint*
s if you want to useshared_ptr
's reference counting machinery. Yes, the deleter can be used to callglDeleteProgram
or whatever you want, but theshared_ptr<GLuint>
will still be storing aGLuint*
.OK, let's forget for a moment that by creating an OpenGL object, the driver almost certainly heap allocated some memory. Let's look just at what you have to do.
By creating a
shared_ptr
that owns some storage, something will be allocated. Namely, the shared block that manages theshared_ptr
's reference count. There's no getting around that. So if you want to useshared_ptr
's reference counting infrastructure, you're going to allocate from somewhere.So the most idiomatic way to do this is to just heap allocate a
GLuint
and use a special deleter that destroys the OpenGL object and deallocates the integer. It's not pretty and it's kind of wasteful, but it's hardly terrible. And if you usemake_shared
, you can make things pretty compact in terms of allocations.Now, you can avoid this allocation by cheating. You can do this:
So here, we're taking an integer and casting it to a pointer value, to be stored within the
shared_ptr
. When you need to use it, you have to reverse the cast to recover the integer value.But judge the following code for yourself:
Does that look like something you want to do frequently? Does it look like something you want to read frequently? Does that look like something that someone else will easily understand what's going on?
Not only that, you can never use
*sp
to get the value, since the pointer value is the value in question.Oh, and the reference counting control block still gets heap allocated, so it's not like you prevent allocating memory or something.
This is not idiomatic C++.