0

Edit: I am referring to std::make_shared and std::make_shared, not their actual constructors. I have edited the title but the text below still talks about constructors. Ignore that I use the word "constructor"

I am trying to learn how smart pointers work behind the scenes but the source files seem to go beyond my understanding, so I figured maybe someone here could help me. How does the declaration and implementation of smart pointers' constructors work?

Normally, you create constructors like this:

Object(SomeType member) : member_(member) {}

Smart pointers obviously use class templates to let the coder choose what type the pointer will be pointing to, but the constructor also takes the arguments needed to construct an object of the type the pointer will be pointing to. How is this achieved? How do you create a constructor like this, when we don't know how many or what types of arguments will be needed, since we don't know the type of the object we will be constructing?

Object(/*???*/) : member_(new SomeType(/*???*/)) {}
JensB
  • 839
  • 4
  • 19
  • 1
    It's called *variadic templates* – Alexey S. Larionov Jan 03 '21 at 10:45
  • 1
    `but the constructor` Smart pointers only take a pointer, objects are constructed outside of smart pointers. As for the sources, see ex. [std::make_unique from glibc](https://github.com/gcc-mirror/gcc/blob/master/libstdc%2B%2B-v3/include/bits/unique_ptr.h#L961) - it's a call to `new` that is variadic, call to `std::unique_ptr` takes the pointer. – KamilCuk Jan 03 '21 at 10:48
  • I really recommend Arthur O'Dwyer's book "Mastering the C++17 STL", which discusses this in detail and gives a sample implementation that is easier to read than the default implementations. – mrks Jan 03 '21 at 10:56

2 Answers2

3

You can use variadic templates and forwarding to forward an arbitrary amount of arguments to another constructor or function. For example:

#include <memory>  // Include std::forward.


template <typename T>
class SmartPointer
{
public:
    template <typename ... Args>
    SmartPointer(Args&& ... args) : the_pointer{new T{std::forward<Args>(args)...}} {}

    // Other methods ...

private:
    T* the_pointer;
};


struct Test
{
    int   a;
    float b;
};

int main()
{
    SmartPointer<Test> pointer { 0, 1.0f };
}

This will forward the arguments 0 and 1.0f to the constructor of Test. The expanded class would look something like this:

class SmartPointer
{
public:
    SmartPointer(int&& a, float&& b) 
        : the_pointer{new Test{std::forward<int&&>(a), std::forward<float&&>(b)}} {}

    // Other methods ...

private:
    Test* the_pointer;
};

Try it online!

Ted Klein Bergman
  • 9,146
  • 4
  • 29
  • 50
1

This is a really robust feature of C++. It is called Variadic templates.

Here is a simple example using

void print() 
{ 
}
template <typename T, typename... Types> 
void print(T var1, Types... var2) 
{ 
    cout << var1 << endl ; 
  
    print(var2...) ; 
} 

Now you can call print with any number of variables of any type. On one condition though, that the type passed implements the operator <<.

Then, you can call print this way:

print(1, 2, 3.14, "Pass me any number of arguments", "I will print\n"); 

C++ standard library is incredibly generic. Understanding the library source code might be challenging if you lack an understanding of generic programming. A good practice exercise can be the following: Implement a sort function that sorts on one call many vectors containing different types.

Oussama Ben Ghorbel
  • 2,132
  • 4
  • 17
  • 34
  • Thanks for the answer! When I try to run the code in your example, I get an error saying that there is no function for the call at the end of the print function. Why is this? – JensB Jan 03 '21 at 11:06
  • Oh of course, my bad. Let me add the function for the base case. Thank you for letting me know. – Oussama Ben Ghorbel Jan 03 '21 at 11:07