C++类:在构造函数体中是否可以选择成员的构造函数?(不双重初始化成员)

C++类:在构造函数体中是否可以选择成员的构造函数?(不双重初始化成员),c++,parameters,constructor,default-constructor,initialization-list,C++,Parameters,Constructor,Default Constructor,Initialization List,考虑这一点: 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

考虑这一点:

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的值使用特定构造函数和特定参数初始化条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++是这样设计的?为什么它会在实际构造函数主体之前强制初始化成员?

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

整个初始化顺序定义如下:

首先,最派生类的构造函数调用虚拟基类子对象的构造函数。虚拟基类先进行深度初始化,从左到右依次初始化。 接下来,直接基类子对象按照它们在类定义中声明的顺序构造。 接下来,将按照非静态成员子对象的顺序构造它们 在类定义中声明。 最后,执行构造函数的主体。
您可以将x设为std::unique\u ptr,然后在ctor中根据需要分配它。

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

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

以下是使用委派构造函数的解决方案:

class Foo
{
    Bar b;
    Foo( Bar b ): b(std::move(b)) { }

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

在委托过程中创建所需的对象,然后调用带条的构造函数。如果您想允许任何人从一个条构造一个Foo,那么您可以将该构造函数公开。

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

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->some\u Bar\u function和some\u function\u taking\u a\u Bar*x.

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

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

这不会导致任何复制或移动,请参见,这是一种惯用的方式

我能想到的第一个解决方案是将一个条直接存储在Foo中,使x成为一个变量成员,并使用placement new构建它。但这需要手工编写所有其他特殊成员函数。@RemyLebeau的可能副本并非如此,因为该用户询问的是与包含类相同的类的构造函数委托,而在我的例子中,Foo和Bar是不同的类。但是,不确定这在本例中是否是一个非常重要的区别。您可以将原样传递给条形构造函数,并根据需要将其委托给其他条形构造函数。它们也不会被销毁、复制或移动,除非相应的操作是琐碎的。@T.C.已经包含了一条关于该操作的注释,但这将需要通过new在免费商店中分配它,对吗?是;这是一个问题吗?在我的情况下不是这样,但它当然与在另一个情况下该类最初的工作方式略有不同。谢谢你的解决方案,谢谢。但是,这个公共构造函数是否也会将Bar添加到其初始化列表中,从而再次导致双重初始化?@JMC no,委托构造函数只调用另一个构造函数,然后在{}中执行代码;它不会初始化任何成员或基类。好的,谢谢,这似乎是一个很好的解决方案。在这种情况下,除了Foo实例的b之外,内存中还会存在b的临时副本吗?只有在执行正确的构造函数后才会删除它?@JMC是的,这会构造一个临时副本,并导致无法删除的移动。@T.C.如果移动很小,编译器仍然可以根据“仿佛”规则直接在b中构造。IDK编译器是否这样做这是完美的,谢谢。不过我不能升级,因为这个账户太新了