【问题标题】:constructor initialization function member instantation构造函数初始化函数成员实例化
【发布时间】:2017-06-06 15:55:54
【问题描述】:

MCVE

#include <map>
class A{
public:
    A(int){

    }
};

class B : public A{
public:
    B()
     : A(filter()){}
    int filter(){
        std::map<int,int> aStuff;
        //new(&m_aStuff)std::map<int,int>;
        m_aStuff = aStuff;
        return 0;
    }
private:
    std::map<int,int> m_aStuff;
};

int main(){
    B b;
    return 0;
}

这在编译时失败,因为 m_aStuff 未初始化。 使用answer我添加了

new(&m_aStuff)std::map<int,int>;

如果您取消注释该行,它将在编译时运行,但是当您离开过滤器类时,m_aStuff 无论如何都会重新初始化。

【问题讨论】:

  • 您能否显示此无效代码,以便我们更好地了解您想要完成的工作?
  • 您是正确的,您不能以这种方式使用未初始化的变量,但还有另一个问题。自动移动可能不会做你想做的事; m_aStuff = std::move(m_aStuff); 可能使 m_aStuff 处于有效但未指定的状态。
  • barA是什么关系? ba 的类型有些混乱。
  • 我不介意提供原始代码,但我试图将其归结为基本代码。未显示的所有内容都可以正常工作。
  • 这似乎是XY problem 的情况。退后一步,解释一下为什么你认为你需要这样做。

标签: c++


【解决方案1】:

我强烈建议您重新考虑设计,但这是我的解决方案:

class A {
public:
  A(int) {}
};

class B : public A {
public:
  struct Dirty {
    std::map<int, int> map;
  };

  B(Dirty dirt = Dirty()) : A(filter(dirt)), m_aStuff(std::move(dirt.map)) {}
  int filter(Dirty& dirt) {
    std::map<int, int> aStuff;
    //new(&m_aStuff)std::map<int,int>;
    dirt.map = aStuff;
    return 0;
  }

private:
  std::map<int, int> m_aStuff;
};

int main() {
  B b;
  return 0;
}

更好的解决方案:

class B : public A {
public:
  B() : B(filter()) {}

private:
  B(std::tuple<int, std::map<int, int>> t)
    : A(std::get<0>(t)), m_aStuff(std::move(std::get<1>(t))) {}

  static std::tuple<int, std::map<int, int>> filter() {
    std::map<int, int> aStuff;
    //new(&m_aStuff)std::map<int,int>;
    return make_tuple(0, aStuff);
  }    

  std::map<int, int> m_aStuff;
};

【讨论】:

  • 谢谢,看来这应该可行。我确实会寻找另一种解决方案,但我对语法糖很感兴趣。
  • 谢谢,看起来确实不错。
  • 如果我曾经发起过投票,那就是再次投票赞成这个答案。它完美地集成在我的代码中并且看起来不错。
【解决方案2】:

作为您未提出的问题“我该如何解决?” Yuki 没有任何详细信息就回答了我将提供一个答案“那里发生了什么?”。

在 C++ 中,对象是从基类构造的。所以顺序如下:

A::A(int); // base class constructor
B::B(); // inherited class constructor

所有成员对象都在初始化列表中初始化:

B::B()
: // Initializer list begin.
A(), // Base class initialized first.
m_aStuff() // Members of current class are initialized next.
{ // End of initializer list.
    // All members are safe to use.
}

这是对象初始化的顺序。正如您在此处看到的,成员 m_aStuff 在基本构造函数 A::A(int) 之后初始化。 构造函数是一个成员函数,因为每个函数都需要评估参数。所以函数int B::filter()在对象初始化之前被调用。这意味着成员变量也没有被初始化。也就是说执行的顺序是:

  1. 致电B::filter()
  2. 修改成员变量B::m_aStuff
  3. 调用基类构造函数A::A(int)
  4. 初始化成员变量B::m_aStuff

显然,第 2 步是在变量初始化之前对其进行修改。根据std::map 的实现,这可能会导致不同的行为(可能未定义)。

实际上以下两种构造是相等的:

B::B() :
A(0)
{}

B::B() :
A(0),
m_aStuff()
{}

但在第二种情况下,您正在显式初始化它,而在第一种情况下,编译器将为您生成此代码。

您的解决方案new(&amp;m_aStuff)std::map&lt;int,int&gt;; 在使用对象之前对其进行初始化,从而使行为更加明确。但接下来,B 类的生成构造函数将启动,B::m_aStuff 将再次被初始化。这会将您的地图设置为初始状态(尽管我可以想象内存泄漏的场景)。

【讨论】:

  • 感谢您写出来,这就是我的想法。我确实在上次编辑中删除了我的问题,但我认为这不再重要了。我想我的问题应该是,如何跳过第 4 步?
  • @turoni 您不能也不应该跳过第 4 步。这是标准和预期的行为。
  • 我知道这将是非常糟糕和危险的,但如果 c++ 允许它,我会喜欢的。有点像“知道你永远不应该这样做,但如果你愿意,我们仍然会给你选择”
猜你喜欢
  • 1970-01-01
  • 2017-06-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-05-02
  • 1970-01-01
相关资源
最近更新 更多