rmaicle

Programming is an endless loop; it's either you break or exit.

Licensed under a Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA).
You are free to copy, reproduce, distribute, display, and make adaptations but you must provide proper attribution. Visit https://creativecommons.org/ or send an email to info@creativecommons.org for more information about the License.

Enforcing Smart Pointer Use for Classes

Date: 2015-12-09 09:07:55 +0000

Enforcing the use of smart pointers when creating instances of a class requires that the class public interface be explicit with the intention. Making constructors private does not work with std::make_unique and std::make_shared.

Here is a minimal example. The error is at line 6; GCC 5.2 says the error is the private constructor. The function std::make_unique must be able to see the constructor to call it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <memory>

class A {
private:
    int number;
    A(int n) : number(n) { }            // error: 'A::A(int)' is private
public:
    int get() const { return number; }
};

int main()
{
    auto x = std::make_unique<A>(3);
    std::cout << x->get() << std::endl;
}

The solution that follows is an attempt to provide a mechanism to enforce the use of smart pointers when creating instances of the class.

Here, the creation of a std::unique_ptr<T> is a simulated version of std::make_unique.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <memory>

class A {
private:
    int number;
    A(int n) : number(n) { }
public:
    template <typename... T>
    static std::unique_ptr<A> create(T&&... args) {
        return std::unique_ptr<A>(new A(std::forward<T>(args)...));
    }
    int get() const { return number; }
};

int main()
{
    auto x = A::create(3);
    std::cout << x->get() << std::endl;
}

The above solution is alright for some but it does not scale. Changing how things are done inside that static function, which could be copy-pasted across multiple class definitions, requires editing all source files that uses such mechanism.

The following uses the approach introduced above and extends the original idea.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <memory>

template <typename T>
class make_unique_ptr_from_private_constructor
{
public:
    template <typename... A>
    static std::unique_ptr<T> create_unique(A&&... args) {
        return std::unique_ptr<T>(new T(std::forward<A>(args)...));
    }
};

template <typename T>
class make_shared_ptr_from_private_constructor
{
public:
    template <typename... A>
    static std::shared_ptr<T> create_shared(A&&... args) {
        std::shared_ptr<T> sp(new T(std::forward<A>(args)...));
        return std::move(sp);
    }
};

Now, this can be used across many classes and the mechanism is localized in one place. The following example shows how to use the template classes above.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
#include <memory>

class A : 
    public make_unique_ptr_from_private_constructor<A>,
    public make_shared_ptr_from_private_constructor<A>
{
    friend class make_unique_ptr_from_private_constructor;
    friend class make_shared_ptr_from_private_constructor;
private:
    int number;
    A(int n) : number(n) { }
public:
    int get() const { return number; }
};

int main()
{
    auto x = A::create_unique(3);
    auto y = A::create_shared(4);
    std::cout << x->get() << std::endl;
    std::cout << y->get() << std::endl;
}

Here is an example showing how to create a std::shared_ptr from a friend class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class B;

class A : protected make_shared_ptr_from_private_constructor<A>
{
    friend class make_shared_ptr_from_private_constructor;
    friend class B;
    // rest of code same as above
};

class B
{
private:
    std::shared_ptr<A> a;
public:
    B(int n) { a = A::create_shared(n); }
    int get() const { return a->get(); }
};

int main()
{
    B b(10);
    std::cout << b.get() << std::endl;
}
Source Code

The source code is in GitHub.

  •  smart pointers
  •  unique_ptr
  •  shared_ptr