简体   繁体   中英

Construct an object, pass it to the base class constructor keeping control of its lifetime

I need to derive a class Child from the class Base with the following requirements:

  1. Class Base constructor accepts a reference to an object of class Foo.
  2. Objects of class Foo should have same lifetime as an object of class Child.
  3. Constructors of Foo, Child and Base can throw, code should adequately destruct what is created so far.
  4. Code is multi-threaded, many constructors of child can be called at the same time
  5. Foo does not have copy constructor, does not have default constructor.

(there are some similarities with How to remind a param passed to the base class constructor? however exception-safety is almost not discussed in the above mentioned question)

While implementing it, I encountered the following difficulties:

A. It seems natural to have object of class Foo to be a member of Child

class Base {
public:
    Base(Foo& foo);
};

class Child: public Base {
public:
    Child(int par);
private:
    Foo m_foo;
};

However, how do I initialize it now in Child constructor?

Child::Child(int par):
Base(Foo(par)),
m_Foo(par) ...

This will pass a ref to temporary to class Base Constructor (will it even compile?) and then initialize m_Foo separately. This is bad.

If not for requirements #4 and #5, I could have used a static method co construct Foo when Base is called and then pass it to m_Foo:

Foo Child::s_Helper(int par) {
    if (!s_FooConstructed) {
        s_Foo = Foo(par);
        s_FooConstructed = true;
    }

    return s_Foo;
}

I would love to use unique_ptr or shared_ptr as Child class member:

class Child: public Base {
public:
    Child(int par);
private:
    unique_ptr<Foo> m_Foo;
    bool m_IsFooConstructed;
    unique_ptr<Foo> x_FooHelper(int par);
};

Child::Child(int par):
    Base(x_FooHelper(par)),
    m_Foo(x_FooHelper(par))
{}

unique_ptr <Foo> Child::x_FooHelper(int par)
{
   if(!m_FooConstructed) {
       m_Foo.reset(new Foo(par));      ///< Problem here
       m_FooConstructed = true;
   }
   return m_Foo;
}

however, here m_Foo is not constructed at the time x_FooHelper is called... same with shared_ptr

I can create a pointer instead:

class Child: public Base {
public:
    Child(int par);
private:
    Foo* m_Foo;
    bool m_IsFooConstructed;
    Foo* x_FooHelper(int par);
};
Child::Child(int par):
    Base(*x_FooHelper(par)),
    m_Foo(x_FooHelper(par))
{}

Foo* Child::x_FooHelper(int par)
{
   if(!m_FooConstructed) {
       m_Foo = new Foo(par);
       m_FooConstructed = true;
   }
   return m_Foo;
}

However here, if Base constructor throws, Foo is already created, and it will not be destroyed as it is kept by the raw pointer (m_Foo) ~Child will also not be called. What can I do?

Use the Base-from-Member Idiom - create another class that owns the Foo as the first base of Child :

class Base {
public:
    Base(Foo& foo);
};

struct FooOwner {
    FooOwner(int par)
        : m_foo(par)
    { }

    Foo m_foo;
};

class Child
    : private FooOwner  // first, so Foo is initialized
    , public Base       //    before Base
{
public:
    Child(int par)
      : FooOwner(par)
      , Base(FooOwner::m_foo)
    { 
        /* something else */
    }
};

The Foo 's lifetime is still tied to Child , and you can safely pass a reference to it to Base - since it definitely will have been constructed before Base .

If you find that too verbose for some reason, you can also use boost::base_from_member to achieve the same thing:

class Child
    : private boost::base_from_member<Foo>
    , public Base
{
    typedef boost::base_from_member<Foo> FooOwner;

    Child(int par)
        : FooOwner(par)
        , Base(FooOwner::member)
    { }
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM