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

Avoid calling constructor of member variable

本文关键字:构造函数 变量 成员 调用      更新时间:2023-10-16

我有以下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动态地分配它。

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

防止在类

中调用数组的默认构造函数
std::aligned_storage<sizeof(T[n]), alignof(T)>::type

或者,您也可以考虑使用union。当然,联合只能用第一个命名成员的构造函数初始化。

例如,

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

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

你不能。

当进入构造代码块时,所有成员变量都是完全构造的。这意味着必须调用这些构造函数。

但是你可以绕过这个限制。

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

我看不到一个好的方法来实现你想要的。这必须是一个解决方案:

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

你的要求是被禁止的——这是正确的。这确保了每个成员都被正确初始化。不要试图绕过它——试着构造你的类,让它们与它一起工作。

的想法:

  • C有一个不做任何事的构造函数
  • C有一个初始化方法,使类可用
  • C跟踪是否正确初始化,如果没有初始化使用,则返回适当的错误。

使用逗号表达式:

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

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

对我来说,指针听起来是唯一干净的解决方案。我看到的唯一其他解决方案是为C提供一个默认构造函数,它不做任何事情,并且在C中有一个初始化方法,稍后您可以自己调用。

m_C。initialize (m_B.getSomeValue());

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

但是为什么不首先初始化m_B(通过适当的构造函数调用,而不是在A::A()中),然后使用初始化的B实例初始化m_C ?这需要稍微重写一下,但我打赌代码清理是值得的。

如果出于代码混乱/异常安全的原因,您不想使用new动态分配它,您可以使用std::unique_ptrstd::auto_ptr来解决这个问题。

避免new的解决方案是编辑C,使其具有两步初始化过程。然后构造函数将构造一个"僵尸"对象,您必须在该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_Cm_B的方法。

我们有了构建模块:

#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()
相关文章: