0

[Edit: Turns out we don't even need reinterpret cast, making this even simpler]

This came up here and I found a better solution using reinterpret cast and shared pointer aliasing constructor. It allows both the ctor and dtor to be private, as well as use of the final specifier.

The reputation system won't let me leave this as an answer in that question, so I had to provide it as another question...

#include <iostream>
#include <memory>

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        return std::shared_ptr<T>(ptr, &ptr->type);
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
};

class X final {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = Factory::make_shared<X>(42);
}

Giving the following output under gcc...

X() addr=0x62bc30                                                                                                      
Type(...) addr=0x62bc30                                                                                                    
X(...) addr=0x62bc50 i=42                                                                                              
Type(...) addr=0x62bc50                                                                                                
~Type()                                                                                                                
~X()                                                                                                                   
~Type()                                                                                                                
~X()  
user1715587
  • 135
  • 8
  • My compiler says that `friend struct Factory::Type;` is an error, because Type is a private member of Factory. Regardless, this is an interesting technique. For the kinds of classes that I use as shared_ptr classes, I use the std::enable_shared_from_this (which some consider an anti-pattern), and have public static factory functions as part of the class itself. – Eljay Oct 27 '18 at 16:49
  • 4
    If it's not a question, don't post it as a question. If you don't have enough reputation to do something, get enough reputation. – n. 'pronouns' m. Oct 27 '18 at 17:09
  • Why the no argument overloads, the variadic versions already take care of that? – Pezo Oct 27 '18 at 18:01
  • @Pezo Habit from an old compiler that didn't support that. Will update accordingly. – user1715587 Oct 27 '18 at 18:24
  • @Eljay I'm using gcc. Which compiler are you using? – user1715587 Oct 27 '18 at 18:32
  • I'm using clang, with pedantic, and `-std=c++17`. – Eljay Oct 27 '18 at 19:45
  • @Eljay Can always make Factory::Type public. Exposing it isn't harmful. – user1715587 Nov 17 '18 at 16:06
  • Why not re-formulate this answer into two parts -- a question and an answer. This would help make your post more compatible with site requirements. – Hovercraft Full Of Eels Nov 17 '18 at 16:30
  • @n.m. I like this answer, and would really like it as an answer to my question. I don't care how much reputation this person has, it's a good answer, and not a 'me to' or spam answer, which is why the question I originally asked was protected. When SO is preventing questions from getting good answers, it's not doing the job it's supposed to be doing. – Omnifarious Nov 17 '18 at 16:38
  • @HovercraftFullOfEels - The OPs question would be a dupe if the OP did this. – Omnifarious Nov 17 '18 at 16:40
  • @Eljay - Upvote this and the OP will have enough rep to post it where it should be. – Omnifarious Nov 17 '18 at 16:42
  • @Pezo - Upvote this and the user will have enough rep to post it where it should be. – Omnifarious Nov 17 '18 at 16:42
  • 1
    @Omnifarious • I had already upvoted it. (I never downvote anything anymore. I often upvote.) BTW, hi! – Eljay Nov 17 '18 at 22:52

1 Answers1

0

Just a followup... The approach above doesn't play well with std::enable_shared_from_this<> because the initial std::shared_ptr<> is to the wrapper and not the type itself. We can address this with an equivalent class that is compatible with the factory...

#include <iostream>
#include <memory>

template<typename T>
class EnableShared {
    friend class Factory;  // member access
public:
    std::shared_ptr<T> shared_from_this() { return weak.lock(); }
protected:
    EnableShared() = default;
    virtual ~EnableShared() = default;
    EnableShared<T>& operator=(const EnableShared<T>&) { return *this; }  // no slicing
private:
    std::weak_ptr<T> weak;
};

class Factory final {
public:
    template<typename T, typename... A>
    static std::shared_ptr<T> make_shared(A&&... args) {
        auto ptr = std::make_shared<Type<T>>(std::forward<A>(args)...);
        auto alt = std::shared_ptr<T>(ptr, &ptr->type);
        assign(std::is_base_of<EnableShared<T>, T>(), alt);
        return alt;
    }
private:
    template<typename T>
    struct Type final {
        template<typename... A>
        Type(A&&... args) : type(std::forward<A>(args)...) { std::cout << "Type(...) addr=" << this << "\n"; }
        ~Type() { std::cout << "~Type()\n"; }
        T type;
    };
    template<typename T>
    static void assign(std::true_type, const std::shared_ptr<T>& ptr) {
        ptr->weak = ptr;
    }
    template<typename T>
    static void assign(std::false_type, const std::shared_ptr<T>&) {}
};

class X final : public EnableShared<X> {
    friend struct Factory::Type<X>;  // factory access
private:
    X()      { std::cout << "X() addr=" << this << "\n"; }
    X(int i) { std::cout << "X(...) addr=" << this << " i=" << i << "\n"; }
    ~X()     { std::cout << "~X()\n"; }
};

int main() {
    auto ptr1 = Factory::make_shared<X>();
    auto ptr2 = ptr1->shared_from_this();
    std::cout << "ptr1=" << ptr1.get() << "\nptr2=" << ptr2.get() << "\n";
}
user1715587
  • 135
  • 8