【问题标题】:C++ classes: Is it possible to select a member's constructor in a constructor body? (Without doubly initializing the member)C++ 类:是否可以在构造函数体中选择成员的构造函数? (没有双重初始化成员)
【发布时间】:2015-06-12 16:40:59
【问题描述】:

考虑一下:

class Foo {
    private:
        Bar x;

    public:
        Foo(int a) { // no initialization here since constructor is dependent on a following if-block
            if (a==0) x=Bar(12,'a', 34);  // some (int, char, int) constructor of Bar
            else x=Bar(13); // (int) constructor of Bar
      }
}

这应该做的是检查参数a的值,并根据a的值使用某个构造函数和某些参数初始化Bar x。然而问题是,这当然被编译器读取为

        Foo(int a): Bar() { // !
            if (a==0) x=Bar(12,'a', 34);  // some Bar(int, char, int) constructor of Bar
            else x=Bar(13); // Bar(int) constructor of Bar
        }

编译器将 Bar() 添加到初始化列表中,因为我省略了它。它现在被双重初始化,一次使用它的 () 构造函数,一次在函数体中。但是如果初始化 Bar(即使使用它的默认构造函数)非常昂贵(关于性能)并且我不能或不想要这种双重初始化呢?

在实际的构造函数体中之前,我如何才能不初始化 Bar x? 如果不可能,我将如何最好地解决这个问题?

补充:为什么 C++ 是这样设计的?为什么它在实际的构造函数体之前强制初始化成员?

【问题讨论】:

  • 我能想到的第一个解决方案(同时将Bar 直接存储在Foo 中)是使x 成为变体成员并使用placement new 构造它。但这需要手动编写所有其他特殊成员函数。
  • @RemyLebeau 并非如此,因为该用户正在询问与包含类相同的类的构造函数委托,而在我的情况下 Foo 和 Bar 是不同的类。不过,不确定在这种情况下这是否是一个非常重要的区别。
  • 您可以将a 原样传递给Bar 构造函数,并根据需要将其委托给其他Bar 构造函数。

标签: c++ parameters constructor default-constructor initialization-list


【解决方案1】:

我认为你问错了问题,而不是试图禁止初始化,你应该这样做,即将你的 ctor 拼写为:

Foo(int a) : x((a==0) ? Bar(12,'a', 34) : Bar(13)) {}

这不会导致任何复制或移动(请参阅here),并且是惯用的。

【讨论】:

  • 这是完美的,谢谢。 (不过我不能投票,因为这个帐户太新了)
【解决方案2】:

不,你不能。当在 ctor 主体中时,成员保证会被初始化。

整个初始化顺序很好地定义为:

  • 首先,最派生类的构造函数调用虚拟基类子对象的构造函数。虚拟基类按深度优先、从左到右的顺序初始化。
  • 接下来,直接基类子对象按照它们在类定义中声明的顺序构造。
  • 接下来,(非静态)成员子对象按照它们的顺序构造 在类定义中声明。
  • 最后,构造函数的主体被执行。

【讨论】:

    【解决方案3】:

    您可以将x 设为std::unique_ptr<Bar>,然后在ctor 中根据需要分配它。

    【讨论】:

    • 但这需要通过 new 将其分配到免费商店,对吗?
    • 是的;有问题吗?
    • 在我的情况下不是这样,但这当然与该类最初打算在另一种情况下的工作方式略有不同。不过,感谢您的解决方案。
    【解决方案4】:

    C++ 是这样设计的,因此构造函数实际上是在构造和初始化事物,而不是让你在脚下开枪。

    尽管如此,从 C++11 开始,您现在可以使用一种后门踢脚技术:除非您明确构造匿名联合的成员,否则它们不会被构造。代码是:

    class Foo
    {
        union 
        {
            Bar x;
        };
    
    public:
        Foo(int a)
        {
            if (a==0) 
                new(&x) Bar(12,'a', 34);
            else
                new(&x) Bar(13);
        }
    
        ~Foo()
        {
             x.~Bar();
        }
    };
    

    要明确,这应该是最后的手段。编译器生成的复制构造函数、移动构造函数等被定义为已删除,因此如果您希望这些操作可用,您也需要实现它们。

    【讨论】:

    • 它们也不会被销毁、复制或移动,除非相应的操作是微不足道的。
    • @T.C.已包含对此的评论
    【解决方案5】:

    在内存方面比匿名联合解决方案稍微贵一些,但更不容易出错并且不会产生额外的复制/移动或动态分配。

    class Foo {
        private:
            std::experimental::optional<Bar> x;
    
        public:
            Foo(int a) {
                if (a==0) x.emplace(12,'a', 34);  // some (int, char, int) constructor of Bar
                else x.emplace(13); // (int) constructor of Bar
          }
    };
    

    此外,在这种情况下,x 将用作指针:x-&gt;some_Bar_function()some_function_taking_a_Bar(*x)

    【讨论】:

      【解决方案6】:

      这是使用委托构造函数的解决方案:

      class Foo
      {
          Bar b;
          Foo( Bar b ): b(std::move(b)) { }
      
      public:
          Foo(int a): Foo( a ? Bar(13) : Bar(12, 'a', 34) ) {}
      };
      

      在委托期间创建所需的对象,然后调用采用Bar 的构造函数。如果您想允许任何人从 Bar 构造 Foo ,那么您可以公开该构造函数。

      【讨论】:

      • 谢谢。但是,这个公共 ctor 不会也将 Bar() 添加到其初始化列表中,从而导致再次进行双重初始化吗?
      • @JMC 不,委托构造函数只调用另一个构造函数(然后执行{}中的代码);它不初始化任何成员或基类。
      • 好的,谢谢,这似乎是一个很好的解决方案。在这种情况下,除了 Foo 实例的 b 之外,内存中是否存在 b 的临时副本,仅在执行“正确”构造函数后才被删除?
      • @JMC 是的,这会构建一个临时的并导致无法省略的移动。
      • @T.C.如果移动是微不足道的,编译器仍然可以在 as-if 规则下直接在b 中构造。 (IDK 编译器是否这样做)
      猜你喜欢
      • 1970-01-01
      • 2021-09-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-11-17
      相关资源
      最近更新 更多