簡體   English   中英

避免調用成員變量的構造函數

[英]Avoid calling constructor of member variable

我有以下C ++ - 類:

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

    private:
    B m_B;
    C m_C;
};

// cpp-File
A::A()
: m_B(1)
{
    m_B.doSomething();
    m_B.doMore();
    m_C = C(m_B.getSomeValue());
}

我現在想避免 class A調用C m_C 任何構造C m_C 因為在A::A()的最后一行,我無論如何都要自己初始化m_C因為我需要先准備m_B 我可以為class B提供一個空的默認構造函數。 但那不是主意。

我已經嘗試將m_C(NULL)添加到A::A()的init-list中。 有時它工作,有時它說沒有構造函數將NULL作為參數。

那么我怎么能讓m_C保持未初始化? 我知道,使用指針, m_C(NULL) 而且我不想使用new動態分配它。

任何想法都表示贊賞。

如何使用本QA中描述的技術?

阻止調用類中的數組的默認構造函數

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

或者,您也可以考慮使用union AFAIK, 聯合只會使用第一個命名成員的構造函數進行初始化。

例如,

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

根據質量保證中提到的標准, c將被零初始化,並且不會調用其構造函數。

我沒有看到實現你想要的方法。 這必須是一個解決方法:

// 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();
}

請確保m_B.doSomething()m_B.doMore()m_B.getSomeValue()不接觸m_C (直接或間接)。


正如@Tobias正確提到的,此解決方案取決於初始化的順序。 您需要確保m_Bm_C的定義按此順序排列。


根據@ Loki的想法更新了代碼。

你不能。

輸入construcotr代碼塊時,所有成員變量都是完整構造的。 這意味着必須調用構造函數。

但你可以解決這個限制。

// 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))
{
}

棘手,但可以做到。

您需要的是一種向成員變量添加行為的方法。 所以變量是初始化的,也可能不是。 我們稱之為“可能”

如果以通用方式執行此操作,則需要模板類來封裝該行為並將其應用於任何類型:

template<class T>
Maybe {
  public:
    Maybe() : m_has(false) {}

    // If we want to start with the value, call the constructor
    Maybe(const T& v) : m_has(true) { new (m_value) T(v); }

    // If we have some value, make sure to call the destructor
    ˜Maybe() { if (m_has) reinterpret_cast<T*>(m_value)->˜T(); }

    // Add the value latter on          
    void setValue(const T& v) {
        if (m_has) {
            reinterpret_cast<T>(*m_value) = v;
        } else {
            m_has = true;
            new (m_value) T(v);
        }
    }

    bool hasValue() const { return m_has; }
    const T& value() const { return reinterpret_cast<T&>(*m_value); }
    T& value() { return reinterpret_cast<T&>(m_value); }

  private:
    bool m_has;
    // Reserve the memory for the object, but dont initialize it - dont call it T
    uint8_t m_value[sizeof(T)];
};

我把代碼寫到了我的頭頂,所以可能會有一些拼寫錯誤或小細節需要調整。 我知道它有效。

現在,只需將您的成員稱為Maybe,然后您就不必創建空構造函數。

你要求的是被禁止的 - 而且是正確的。 這可確保正確初始化每個成員。 不要試圖解決它 - 嘗試構建他們使用它的類。

理念:

  • C有一個什么都不做的構造函數
  • C有一個初始化方法,使類可用
  • C跟蹤它是否已正確初始化,如果在未初始化的情況下使用,則返回適當的錯誤。

這里我們有構建基塊:

#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;}
};

如果你想為B做一種新的構造,考慮做一個派生類:

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

現在使用從B派生的B1類:

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

然后:

int main()
{
  A a;
}

輸出:

B::B(1)
B::doSomething()
B::doMore()
C::C(42)
A::A()

如果您不想使用new動態分配代碼混亂/異常安全原因,可以使用std::unique_ptrstd::auto_ptr來解決此問題。

避免new的解決方案是編輯C以進行兩步初始化過程。 然后構造函數將構造一個“zombie”對象,並且您必須在該m_C實例上調用Initialize方法來完成初始化。 這與您在可以將NULL傳遞給構造函數的現有案例類似,然后返回初始化對象。

編輯:

我之前想過這個(盡管它看起來很像其他人的解決方案)。 但是我必須得到一些確認,在我添加這個解決方案之前這不會破壞--C ++可能非常棘手,而且我不經常使用它:)

這比我的其他建議更清晰,並且不要求你搞亂任何實現但是A

在初始化時只需使用靜態方法作為中間人:

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))
{
}

請注意,這意味着您不能允許m_B依賴於AC的實例,而此答案頂部的解決方案可能允許您將Am_C傳遞給m_B的方法。

最簡單的是存儲指向BC指針。 這些可以初始化為0,省略任何結構。 注意不要取消引用空指針並在A的析構函數中刪除它(或使用std::unique_ptr / boost::scoped_ptr )。

但是為什么不首先初始化m_B (通過正確的構造函數調用,而不是在A::A() ,然后使用該初始化的B實例初始化m_C ?它會調用一個小的重寫,但我敢打賭它值得代碼清理。

指針聽起來像是我唯一干凈的解決方案。 我看到的唯一的另一個解決方案是為C設置一個默認構造函數,它什么都不做,並且在C中有一個初始化方法,你稍后會自己調用。

m_C.Initialise(m_B.getSomeValue());

只需使用逗號表達式:

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

顯然,正如其他人所解釋的那樣, m_B最好在m_C之前聲明,否則m_B.doSomething()調用未定義的行為。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM