避免调用成员变量的构造函数

时间:2011-10-20 09:35:37

标签: c++ initializer-list

我有以下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的任何构造函数。因为在A::A()的最后一行,我无论如何都要自己初始化m_C因为我需要先准备m_B。我可以为class B提供一个空的默认构造函数。但那不是主意。

我已尝试将m_C(NULL)添加到A::A()的初始列表中。有时它有效,有时它说没有构造函数以NULL为参数。

那么我怎样才能让m_C未初始化?我知道,使用指针,m_C(NULL) - 方式有效。我不想使用new动态分配它。

任何想法都表示赞赏。

10 个答案:

答案 0 :(得分:4)

我没有看到良好的方式来实现您想要的效果。这必须是一种解决方法:

// 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的想法更新了代码。

答案 1 :(得分:3)

你不能。

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

答案 2 :(得分:3)

整蛊,但可以做到。

您需要的是一种向成员变量添加行为的方法。所以变量是初始化的,也可能不是。我们称之为“可能”

如果以通用方式执行此操作,则需要模板类来封装该行为并将其应用于任何类型:

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,然后您就不必创建空构造函数。

答案 3 :(得分:3)

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

Prevent calls to default constructor for an array inside class

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

或者,您也可以考虑使用union。 AFAIK,unions will be initialized only with first named member's constructor.

例如,

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

根据QA中提到的标准,c将被零初始化,并且不会调用其构造函数。

答案 4 :(得分:2)

你要求的是被禁止的 - 而且是正确的。这可确保正确初始化每个成员。不要试图解决它 - 尝试构建他们使用它的类。

点子:

  • C有一个什么都不做的构造函数
  • C有一个使类可用的初始化方法
  • C跟踪它是否已正确初始化,如果在未初始化的情况下使用,则返回相应的错误。

答案 5 :(得分:0)

如果您不希望使用new动态分配代码混乱/异常安全原因,可以使用std::unique_ptrstd::auto_ptr来解决此问题。

避免new的解决方案是编辑C以进行两步初始化过程。然后,构造函数将构造一个“zombie”对象,您必须在该Initialize实例上调用m_C方法才能完成初始化。这类似于您找到的可以将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的方法。

答案 6 :(得分:0)

最简单的是存储指向BC的指针。这些可以初始化为0,省略任何结构。注意不要取消引用空指针并在A的析构函数中删除它(或使用std::unique_ptr / boost::scoped_ptr)。

但为什么不首先初始化m_B(通过正确的构造函数调用,而不是A::A(),然后使用该初始化的B实例初始化m_C?它会调用对于一个小的重写,但我敢打赌它值得代码清理。

答案 7 :(得分:0)

指针听起来像是我唯一干净的解决方案。我看到的唯一其他解决方案是为C设置一个默认构造函数,它不执行任何操作,并且在C中使用初始化方法,稍后会自行调用。

m_C.Initialise(m_B.getSomeValue());

答案 8 :(得分:0)

只需使用逗号表达式:

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

显然,正如其他人所解释的那样,m_B最好在m_C之前声明{else} m_B.doSomething()调用未定义的行为。

答案 9 :(得分:0)

这里有构建基块:

#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()