Enforcing Smart Pointer Use for Classes
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.