kzsnyk
kzsnyk

Reputation: 2211

How to build a class in one call if initialization need shared_from_this?

I have a class that need to call shared_from_this() for its initialization. As it is not possible to directly call this function inside the ctor, then i have to do it in 2 steps :

  1. build the class using a static factory function
  2. call an init function of the class so that the object is ready to use

It works however ideally, i would like the client code only to make one single call for it - and make the init function private as it is a part of the construction.

Is this possible ?

There is also a side note in the comments following exception: bad_weak_ptr while shared_from_this because it does not work in my case ( but maybe it's another question )

#include <iostream>
#include <memory>

class Foo;

void globalInit(std::shared_ptr<Foo> foo, int val)
{
    (void)foo;
    std::cout << __PRETTY_FUNCTION__ << val <<std::endl;
}

class Foo : public std::enable_shared_from_this<Foo>
{
public:
    static std::shared_ptr<Foo> create(int val) {
        return std::shared_ptr<Foo>(new Foo(val));
        /*
         * side note : if i use return std::make_shared<Foo>(val);
         * then i have a compiler error with g++ (Debian 6.3.0-18)
         * "Foo::Foo(int) is private within this context"
         */
    }

    // it should not be public ..
    void init() {
        // during init, i need to call shared_from_this()
        // and access private members
        globalInit(shared_from_this(), m_val);
    }

private:
    // i cannot call shared_from_this() in the ctor
    explicit Foo(int val) : m_val(val) {}

private:
    int m_val;
};

int main(int argc, char *argv[])
{
    (void)argc;
    (void)argv;

    // create the object
    std::shared_ptr<Foo> foo = Foo::create(0);
    // initialize it
    foo->init();

    // now it's ready to use
    // ...

    return 0;
}

Thank you.

Upvotes: 1

Views: 120

Answers (2)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136286

If you:

  1. don't need std::week_ptr,
  2. can use boost libraries,

than boost::intrusive_ptr is all around a better choice than std::shared_ptr because:

  1. sizeof(boost::intrusive_ptr<T>) == sizeof(std::shared_ptr<T>) / 2.
  2. You explicitly control whether the reference counter is atomic or not. Objects that never cross thread boundaries do not need the more expensive atomic increment/decrement. std::shared_ptr always uses two atomic counters (one for the object, another for the control block itself).
  3. No extra memory allocation (like with std::make_shared).
  4. You can pass around plain pointers and references to T and make boost::intrusive_ptr<T> when you need them. This may be dangerous because you must be sure that the object is heap allocated. Nevertheless, you can, if you need to, no factory is needed.

Example:

#include <boost/smart_ptr/intrusive_ptr.hpp>
#include <boost/smart_ptr/intrusive_ref_counter.hpp>

#include <iostream>

struct Foo;
void globalInit(Foo*);

struct Foo : boost::intrusive_ref_counter<Foo, boost::thread_unsafe_counter> {
    Foo() {
        globalInit(this);
    }
};

boost::intrusive_ptr<Foo> global_copy;

void globalInit(Foo* foo) {
    std::cout << __PRETTY_FUNCTION__ << '\n';
    global_copy.reset(foo);
}

int main() {
    boost::intrusive_ptr<Foo> a(new Foo);
    std::cout << (a == global_copy) << '\n';
}

Upvotes: 1

Jarod42
Jarod42

Reputation: 217398

You might call init from your factory:

class Foo : public std::enable_shared_from_this<Foo>
{
private:
    class private_key{
    private:
        friend class Foo;

        private_key() {}
    };

public:
    static std::shared_ptr<Foo> create(int val) {
        auto p = std::make_shared<Foo>(private_key{}, val);

        p->init(); // Or directly globalInit(p, p->m_val);
        return p;
    }

private:
    void init() {
        // during init, I need to call shared_from_this()
        // and access private members
        globalInit(shared_from_this(), m_val);
    }

public: // But only callable internally thanks to private_key
    Foo(private_key, int val) : m_val(val) {}

private:
    int m_val;
};

Demo

Upvotes: 3

Related Questions