Is this usage of shared_ptr correct?

I try to use shared_ptr to manage data of several types. So I use shared_ptr to hold different data type. The code below illustrates the idea:

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
struct MyClass
{
    int num;
    std::shared_ptr<void> ptr;
};

std::shared_ptr<void> f1(char c, int n)
{
    switch(c) //There are more cases. Here only for illustration. I 
              //want to return pointer of different data types.
    {
        case 'i':
        {
            return std::make_shared<int>(n);
            break;
        }
        case 's':
        {
            return std::make_shared<std::string>(to_string(n));
            break;
        }
        default:
        return NULL;
    }
}

vector<MyClass> f2()
{
    vector<MyClass> vec;
    char c;
    for(int i = 0; i < 10; i++)
    {
        c = 'i';//getchar();
        MyClass data;
        data.num = i;
        data.ptr = f1(c, i);
        vec.push_back(data);
    }
    return vec;

}

int main() {
    vector<MyClass> v;
    v = f2();
    for(auto e : v)
    {
        cout << e.num << " " << *(static_pointer_cast<std::string>(e.ptr)) << endl;
    }
    return 0;
}


Is my use of shared_ptr a good practice? Are there better ways?
Last edited on
std::shared_ptr<void> cannot release the resource it owns because it doesn't know its type. You need to provide a deleter function to std::shared_ptr. See: http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/

Alternatively, you could write wrapper classes Int and String that inherit from an Object class. That would be a lot safer. Or you could use Boost.Any.
Last edited on
> Is my use of shared_ptr a good practice?

The first point to note is that there is no resource leak; even after conversion to std::shared_ptr<void>, the resources would be released correctly. (std::shared_ptr<void> stores a copy of the type-erased deleter of the original shared pointer that was created with std::make_shared<>, and applies that deleter when the last reference to the object is released).

The second point is that there are better design alternatives to exploit the strong type system that C++ has: switching on a type field and casting appropriately depending on that engenders hard-to-maintain, error-prone code. Using std::variant<> (C++17) or boost::variant<> would be a good design choice here.
If I use std::variant<>, I also need to pass in the different data types, then it is not much better than simply using new/delete, right?
> If I use std::variant<>, I also need to pass in the different data types,

Typically, we would use std::visit to automagically discriminate between different types.
For instance:

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
#include <iostream>
#include <vector>
#include <string>
#include <variant>

using int_str_or_dbl = std::variant< int, std::string, double > ;

struct twice
{
    template < typename T >
    int_str_or_dbl operator() ( T&& v ) const { return v + v ; }

    int_str_or_dbl operator() ( const std::string& str ) const
    { return str + " - " + str ; }
};

int main()
{
    std::vector<int_str_or_dbl> seq ;
    seq.push_back(22) ;
    seq.push_back( "hello world!" ) ;
    seq.push_back(46.82) ;

    const auto print_it =  [] ( const auto& v ) { std::cout << v << '\n' ; } ;
    for( const auto& var : seq ) std::visit( print_it, var );
    for( const auto& var : seq ) std::visit( print_it, std::visit( twice(), var ) ) ;
}

http://coliru.stacked-crooked.com/a/bae79a27f16b092e
Topic archived. No new replies allowed.