如何在构造函数中初始化C++类的数据成员?

119
我有一个类,其中有几个类类型的数据成员。当声明时,我不希望调用这些成员的构造函数,所以我试图显式地保留对象的指针。
我想也许我可以这样做,在初始化数据成员时立即调用构造函数:
class MyClass {
    public:
        MyClass(int n);
    private:
        AnotherClass another(100); // Construct AnotherClass right away!
};

但是我希望MyClass的构造函数调用AnotherClass的构造函数。以下是我的代码示例:

BigMommaClass.h

#include "ThingOne.h"
#include "ThingTwo.h"

class BigMommaClass {
    public:
        BigMommaClass(int numba1, int numba2);

    private:
        ThingOne* ThingOne;
        ThingTwo* ThingTwo;
};

BigMommaClass.cpp

#include "BigMommaClass.h"

BigMommaClass::BigMommaClass(int numba1, int numba2) {
    this->ThingOne = ThingOne(100);
    this->ThingTwo = ThingTwo(numba1, numba2);
}

这是我在尝试编译时遇到的错误:
g++ -Wall -c -Iclasses -o objects/BigMommaClass.o classes/BigMommaClass.cpp
In file included from classes/BigMommaClass.cpp:1:0:
classes/BigMommaClass.h:12:8: error: declaration of âThingTwo* BigMommaClass::ThingTwoâ
classes/ThingTwo.h:1:11: error: changes meaning of âThingTwoâ from âclass ThingTwoâ
classes/BigMommaClass.cpp: In constructor âBigMommaClass::BigMommaClass(int, int)â:
classes/BigMommaClass.cpp:4:30: error: cannot convert âThingOneâ to âThingOne*â in assignment
classes/BigMommaClass.cpp:5:37: error: â((BigMommaClass*)this)->BigMommaClass::ThingTwoâ cannot be used as a function
make: *** [BigMommaClass.o] Error 1

我是不是在用正确的方法,但是语法错了?还是我应该从另一个角度来解决这个问题?

你只是想从那里调用它,以便可以使用这些参数吗? - chris
@chris,目前为止,我认为这就是我正在做的全部。但是,如果我需要在传递参数之前执行某些操作,比如将“numba1”和“numba2”相加并将总和传递给成员变量构造函数,我也想知道如何做到这一点。 - Logical Fallacy
2
你的直接错误是将一个对象赋值给了一个指针(你需要一个 new,但是有更好的替代方法)。然而,手头的问题可以通过成员初始化器来解决。 - chris
1
私有的:AnotherClass another(100); // 这会立即构造 AnotherClass! 真的吗?我的编译器不接受这个,除非声明在函数内部。这行会被解释为一个函数声明,而编译器期望一个参数列表,而不是一个常量或变量。 - Bart
5个回答

129

您可以在成员初始化列表中指定如何初始化成员:

BigMommaClass {
    BigMommaClass(int, int);

private:
    ThingOne thingOne;
    ThingTwo thingTwo;
};

BigMommaClass::BigMommaClass(int numba1, int numba2)
    : thingOne(numba1 + numba2), thingTwo(numba1, numba2) {}

32
这个语法让我困扰,因为我无法在构造函数中进行任何真正的工作来传递给其他构造函数。构造函数可能应该是轻量级的,所以我很难想出一个真实世界的例子,但我发现自己希望这个语法看起来更像this.thingOne = new ThingOne(100);,因为那样提供了更多的灵活性。但我离题了。 - Logical Fallacy
thingOne和thingTwo必须在BigMommaClass的构造函数中初始化吗?还是可以在其他一些在构造函数之后调用的函数中完成初始化? - jigglypuff
1
@nobism,对于初始化而不是赋值,必须在构造函数中完成。您可以执行类似于 : thingOne(calculateThing1(numba1, numba2)) 的操作。在极少数情况下,您可能需要初始化,但无法在初始化程序列表中完成。在这种情况下,可以使用类似于std::optionalts::deferred_construction的东西。然而,这个决定需要考虑很多因素。 - chris

42

您试图使用operator=来创建一个ThingOne,这样是行不通的(语法不正确)。另外,您正在使用一个类名作为变量名,即ThingOne * ThingOne。首先,让我们修复变量名称:

private:
    ThingOne* t1;
    ThingTwo* t2;

由于这些是指针,它们必须指向某些东西。如果该对象尚未构造,则需要在BigMommaClass构造函数中显式地使用 new 进行构造:

BigMommaClass::BigMommaClass(int n1, int n2)
{
    t1 = new ThingOne(100);
    t2 = new ThingTwo(n1, n2);
}

通常情况下,初始化列表更适合用于构造对象,因此代码应该类似于:

BigMommaClass::BigMommaClass(int n1, int n2)
    : t1(new ThingOne(100)), t2(new ThingTwo(n1, n2))
{ }

2
谢谢。我应该使用ThingOne* t1还是只使用ThingOne t1 - Logical Fallacy
3
使用后者。不必要的动态分配是不好的。如果真的需要,智能指针是一个更好的选择。 - chris
我们需要在BigMommaClass中添加一个析构函数来删除t1t2对象吗? - RHertel
@RHertel 是的,它应该可以,不过它应该使用 unique_ptrmake_unique 而不是原始的 new,以消除那个需要 - 我只是不想一次性引入太多概念。 - Yuushi
谢谢你,@Yuushi。我最终根据你的答案得出了一个解决方案,不过我使用了shared_ptrmake_shared - RHertel
显示剩余5条评论

13

这个问题有点老了,但是在C++11中,在初始化成员变量之前“做更多的工作”的另一种方法如下:

BigMommaClass::BigMommaClass(int numba1, int numba2)
    : thingOne([](int n1, int n2){return n1+n2;}(numba1,numba2)),
      thingTwo(numba1, numba2) {}
上面的Lambda函数将被调用,并将结果传递给thingOnes构造函数。当然,您可以使Lambda函数尽可能复杂。

括号不平衡。 - Ted

7
我知道这已经是五年后的事了,但是上面的回答没有解决你软件中的问题。(嗯,Yuushi的回答解决了我的问题,但是我打完字才发现 - 天哪!)他们回答了标题中的问题“如何在构造函数中初始化C++对象成员变量?”而你的问题是:“我是否使用了正确的方法但错误的语法?或者我应该从不同的方向来考虑这个问题?”
编程风格在很大程度上是一种观点,但与其试图将尽可能多的操作放入构造函数中,不如将构造函数最小化,通常有一个单独的初始化函数。没有必要试图将所有的初始化都塞进构造函数中,更不用说在构造函数初始化列表中强制执行某些操作了。
所以,重点是,你的软件有什么问题呢?
private:
    ThingOne* ThingOne;
    ThingTwo* ThingTwo;

请注意,在这些行之后,ThingOne(和ThingTwo)现在根据上下文具有两个意义。
在BigMommaClass之外,ThingOne是你使用#include "ThingOne.h"创建的类。
在BigMommaClass内部,ThingOne是一个指针。
假定编译器能够理解这些行并且不会陷入一个循环中,认为ThingOne是指向某个指针的指针,该指针又是指向某个指针的指针...
稍后,当你写下:
this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);

记住,在 BigMommaClass 内部,你的 ThingOne 是一个指针。
如果你改变指针的声明并加上前缀(p),请注意。
private:
    ThingOne* pThingOne;
    ThingTwo* pThingTwo;

那么ThingOne将一直指向该类,而pThingOne则指向该指针。

然后可以重写:

this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);

as

pThingOne = new ThingOne(100);
pThingTwo = new ThingTwo(numba1, numba2);

这样可以解决两个问题:双重意义问题和缺失的new。如果你愿意,可以保留this->

有了这个,我可以在我的C++程序中添加以下行,并且它会编译得非常好。

class ThingOne{public:ThingOne(int n){};};
class ThingTwo{public:ThingTwo(int x, int y){};};

class BigMommaClass {

    public:
            BigMommaClass(int numba1, int numba2);

    private:
            ThingOne* pThingOne;
            ThingTwo* pThingTwo;
};

BigMommaClass::BigMommaClass(int numba1, int numba2)
{
    pThingOne = new ThingOne(numba1 + numba2);
    pThingTwo = new ThingTwo(numba1, numba2);
};

当你编写代码时

this->ThingOne = ThingOne(100);
this->ThingTwo = ThingTwo(numba1, numba2);

使用this->告诉编译器左边的ThingOne是指针,但在此时我们正在BigMommaClass内部,这是不必要的。

问题出现在等号右侧,ThingOne指的是类。因此,解决问题的另一种方法是这样写:

this->ThingOne = new ::ThingOne(100);
this->ThingTwo = new ::ThingTwo(numba1, numba2);

或者简单地说
ThingOne = new ::ThingOne(100);
ThingTwo = new ::ThingTwo(numba1, numba2);

使用 :: 来改变编译器对标识符的解释。


4

关于chris提出的第一个(伟大的)答案,他提出了一种解决方案,用于类成员作为“真复合”成员的情况(即- 不是作为指针 也不是 引用):

这个注释有点大,所以我将在此处使用一些示例代码进行演示。

当您选择像我提到的那样持有成员时,您还必须记住以下两件事:

对于每个“组合对象”,如果它没有默认构造函数 - 你必须在"父类"的所有构造函数的初始化列表中初始化它(如原始示例中的`BigMommaClass`或`MyClass`以及下面代码中的`MyClass`),如果有多个(请参见下面示例中的`InnerClass1`)。这意味着,只有当你启用`InnerClass1`的默认构造函数时,你才能“注释掉”`m_innerClass1(a)`和`m_innerClass1(15)`。
对于每个“组合对象”,如果它有默认构造函数 - 你可以在初始化列表中初始化它,但如果不这样做,它也可以工作(请参见下面示例中的`InnerClass2`)。

参考样例代码(在Ubuntu 18.04(Bionic Beaver)下使用g++版本7.3.0编译):

#include <iostream>

class InnerClass1
{
    public:
        InnerClass1(int a) : m_a(a)
        {
            std::cout << "InnerClass1::InnerClass1 - set m_a:" << m_a << '\n';
        }

        /* No default constructor
        InnerClass1() : m_a(15)
        {
            std::cout << "InnerClass1::InnerClass1() - set m_a:" << m_a << '\n';
        }
        */

        ~InnerClass1()
        {
            std::cout << "InnerClass1::~InnerClass1" << '\n';
        }

    private:
        int m_a;
};

class InnerClass2
{
    public:
        InnerClass2(int a) : m_a(a)
        {
            std::cout << "InnerClass2::InnerClass2 - set m_a:" << m_a << '\n';
        }

        InnerClass2() : m_a(15)
        {
            std::cout << "InnerClass2::InnerClass2() - set m_a:" << m_a << '\n';
        }

        ~InnerClass2()
        {
            std::cout << "InnerClass2::~InnerClass2" << '\n';
        }

    private:
        int m_a;
};

class MyClass
{
    public:
        MyClass(int a, int b) : m_innerClass1(a), /* m_innerClass2(a),*/ m_b(b)
        {
            std::cout << "MyClass::MyClass(int b) - set m_b to:" << m_b << '\n';
        }

         MyClass() : m_innerClass1(15), /*m_innerClass2(15),*/ m_b(17)
        {
            std::cout << "MyClass::MyClass() - m_b:" << m_b << '\n';
        }

        ~MyClass()
        {
            std::cout << "MyClass::~MyClass" << '\n';
        }

    private:
        InnerClass1 m_innerClass1;
        InnerClass2 m_innerClass2;
        int m_b;
};

int main(int argc, char** argv)
{
    std::cout << "main - start" << '\n';

    MyClass obj;

    std::cout << "main - end" << '\n';
    return 0;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接