Avoid calling constructor of member variable

后端 未结 9 1548
礼貌的吻别
礼貌的吻别 2021-02-19 13:14

I have the following C++-class:

// Header-File
class A
{
    public:
    A();

    private:
    B m_B;
    C m_C;
};

// cpp-File
A::A()
: m_B(1)
{
    m_B.doSom         


        
相关标签:
9条回答
  • 2021-02-19 13:54

    How about using technique described in this QA?

    Prevent calls to default constructor for an array inside class

    std::aligned_storage<sizeof(T[n]), alignof(T)>::type
    

    Or, you also can consider using of union. AFAIK, unions will be initialized only with first named member's constructor.

    For example,

    union
    {
       uint8_t _nothing = 0; 
       C c;
    };
    

    According to the standard mentioned in the QA, c will be zero-initialized, and its constructor will not be called.

    0 讨论(0)
  • 2021-02-19 13:59

    If you don't want to allocate it dynamically using new for code clutter/exception safety reasons, you can use a std::unique_ptr or std::auto_ptr to solve this problem.

    A solution that avoids new is to edit C to have a two-step initialization process. The constructor would then construct a "zombie" object, and you'd have to call an Initialize method on that m_C instance to finish your initialization. This is similar to the existing cases you found where you could pass NULL to the constructor, and later go back to initialize the object.

    Edit:

    I thought of this earlier (even though it looks much like other people's solutions). But I had to get some confirmation that this wouldn't break before I added this solution - C++ can be quite tricky, and I don't use it very often :)

    This is cleaner than my other suggestions, and doesn't require you to mess with any implementation but that of A.

    Simply use a static method as the middle-man on your initialization:

    class A
    {
    public:
        A();
    
    private:
        static int InitFromB(B& b)
        {
            b.doSomething();
            b.doMore();
            return b.getSomeValue();
        }
    
        // m_B must be initialized before m_C
        B m_B;
        C m_C;
    };
    
    A::A()
        : m_B(1)
        , m_C(InitFromB(m_B))
    {
    }
    

    Note that this means you can't allow m_B to depend on the instance of A or C at all, whereas the solutions at the top of this answer might allow you to pass A or m_C into m_B's methods.

    0 讨论(0)
  • 2021-02-19 14:05

    I don't see a good way to achieve what you want. This must be a workaround:

    // Header-File
    class A
    {
        public:
        A();
    
        private:
        B m_B;
        C m_C;
        static int prepareC(B& b);
    };
    
    // cpp-File
    A::A()
    : m_B(1)
    , m_C(prepareC(m_B))
    {
    }
    
    int A::prepareC(B& b)
    {
        b.doSomething();
        b.doMore();
        return b.getSomeValue();
    }
    

    Please ensure that m_B.doSomething(), m_B.doMore() and m_B.getSomeValue() don't touch m_C (directly or indirectly).


    As @Tobias correctly mentions, this solution depends on the order of initialization. You need to ensure that the definitions of m_B and m_C are in this order.


    Updated the code according to @Loki's idea.

    0 讨论(0)
  • 2021-02-19 14:07

    Here we have the building blocks:

    #include <iostream>
    
    class C
    {
    public:
      C(int i){std::cout << "C::C(" << i << ")" << std::endl;}
    };
    
    class B
    {
    public:
      B(int i){std::cout << "B::B(" << i << ")" << std::endl;}
      void doSomething(){std::cout << "B::doSomething()" << std::endl;}
      void doMore(){std::cout << "B::doMore()" << std::endl;}
      int getSomeValue(){return 42;}
    };
    

    If you want to make a new kind of construction for B consider making a derived class:

    class B1 : public B
    {
    public:
      B1() : B(1)
      {
        doSomething();
        doMore();
      }
    };
    

    Now use the class B1 that is derived from B:

    class A
    {
    private:
      B1 _b;
      C _c;
    public:
      A() : _c(_b.getSomeValue()){std::cout << "A::A()" << std::endl;}
    };
    

    And then:

    int main()
    {
      A a;
    }
    

    Output:

    B::B(1)
    B::doSomething()
    B::doMore()
    C::C(42)
    A::A()
    
    0 讨论(0)
  • 2021-02-19 14:08

    Just use comma expressions:

    A::A()
      : m_B(1)
      , m_c(m_B.doSomething(), m_B.doMore(), m_B.getSomeValue())
    {
    }
    

    Obviously, as others have explained, m_B better be declared before m_C else m_B.doSomething() invokes undefined behavior.

    0 讨论(0)
  • 2021-02-19 14:16

    You can't.

    All member variables are full constructed when the construcotr code block is entered. This means there constructors must be called.

    But you can work around this restriction.

    // Header-File
    class A
    {
        struct Initer
        {
             Initer(B& b)
                 : m_b(b)
             {
                 m_b.doSomething();
                 m_b.doMore();
             }
             operator int()  // assuming getSomeValue() returns int.
             {
                 return m_b.getSomeValue();
             }
             B& m_b;
        };
        public:
        A();
    
        private:   // order important.
        B m_B;
        C m_C;
    };
    
    
    // cpp-File
    A::A()
    : m_B(1)
    , m_C(Initer(m_B))
    {
    }
    
    0 讨论(0)
提交回复
热议问题