smart pointer incomplete type

//file: a.h
1
2
3
4
5
6
7
8
#pragma once
#include <iostream>

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

//file: b.h
1
2
3
4
5
6
7
8
9
#pragma once
#include <memory>

class A;
struct B {
	B();
	//A is incomplete type
	std::shared_ptr<A> pa_;
};

//b.cpp
1
2
3
4
5
6
#include "b.h"
#include "a.h"

B::B():pa_{ new A{} }
{
}

//main.cpp:main function
1
2
3
4
5
6
7
#include "b.h"

int main() {
	{
		B b;
	}
}


I think that "~A" will not be outputed, because class A is an incomplete type in B's destructor.
But gcc 6.2 and vs2015 both output "~A".
The (shared) control block of a std::shared_ptr<> object holds the deleter; the deleter is type-erased. Unlike std::unique_ptr<>, the deleter is not a template parameter.

The type-erased deleter is instantiated when an instance of the shared_ptr<> is first attached to a shared object; at that point, what is the associated deleter would be known.

Type-erasure: http://davekilian.com/cpp-type-erasure.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#include <iostream>
#include <memory>

struct A ;

struct B
{
    B() ;
    B(int) ;

    std::shared_ptr<A> pa ;
    static A sa ;
};

struct A
{
    static void* operator new( std::size_t sz ) { return ::operator new(sz) ; }
    static void operator delete( void* ptr )
    {
        std::cout << "A::operator delete\n" ;
        ::operator delete(ptr) ;
    }

    static void* operator new[]( std::size_t sz ) { return ::operator new[](sz) ; }
    static void operator delete[]( void* ptr ) { ::operator delete(ptr) ; }
};

A B::sa ;

B::B() : pa( new A ) {} // default deleter (delete A)

// custom do_nothing deleter
B::B(int) : pa( std::addressof(sa), []( A* ) { std::cout << "do nothing\n" ; } ) {}

int main()
{
    {
        std::cout << "instantiate with default deleter\n" ;
        B b ;
    } // A::operator delete
    std::cout << "\n-----------------------\n" ;
    {
        std::cout << "instantiate with do nothing deleter\n" ;
        B b(100) ;
    } // do nothing
    std::cout << "\n-----------------------\n" ;

    std::shared_ptr<A> pa ; // the deleter does not contribute to the type of pa

    pa = std::shared_ptr<A>( new A ) ;
    // the type-erased deleter associated with pa is now the default deleter
    pa = nullptr ; // A::operator delete
    std::cout << "\n-----------------------\n" ;

    pa = std::shared_ptr<A>( nullptr, []( A* ) { std::cout << "hello world!\n" ; } ) ;
    // the type-erased deleter associated with pa is now our 'hello world!' deleter
    pa = nullptr ; // Hello World!
}

http://coliru.stacked-crooked.com/a/a9bacf56a3f2875e
http://rextester.com/TUDBJ94142

Needles to say, the allocator that the control block holds is also type-erased
(or else things like std::allocate_shared<>() wouldn't work.)
Last edited on
Topic archived. No new replies allowed.