如何在派生类构造函数中初始化基类成员变量?

176

为什么我不能这样做?

class A
{
public:
    int a, b;
};

class B : public A
{
    B() : A(), a(0), b(0)
    {
    }

};

10
你是在问为什么你不能这样做,这是一个语言设计的问题,还是在问如何绕过这种语言限制? - Rob Kennedy
我认为可能有一种特殊的方法可以做到这一点,而不必使用基本构造函数,但我并不知道。 - amrhassan
2
派生类构造函数运行时,基类成员已经被初始化。如果您有访问权限,可以分配它们,或为它们调用设置器,或者在适当的情况下为它们提供值以传递给基类构造函数。但是,在派生类中,您不能对它们进行初始化。 - user207421
7个回答

195

你无法在类 B 中初始化 ab,因为它们不是 B 的成员,它们是 A 的成员,因此只有 A 可以初始化它们。你可以将它们设置为公共的,然后在 B 中进行赋值,但这不是一个推荐的选项,因为它会破坏封装性。相反,可以在 A 中创建一个构造函数,允许 B (或任何 A 的子类)来初始化它们:

class A 
{
protected:
    A(int a, int b) : a(a), b(b) {} // Accessible to derived classes
    // Change "protected" to "public" to allow others to instantiate A.
private:
    int a, b; // Keep these variables private in A
};

class B : public A 
{
public:
    B() : A(0, 0) // Calls A's constructor, initializing a and b in A to 0.
    {
    } 
};

46
虽然你的例子是正确的,但是你的解释有误导性。不是因为 ab 是私有的所以不能在 B::B()初始化它们,而是因为它们不是class B的成员变量。如果你将它们声明为公有或保护的,你可以在 B::B() 的函数体内对它们进行赋值 - R Samuel Klatchko
3
此外,你的解决方案使得A类不再是聚合体(non-aggregate),这可能很重要,因此必须加以说明。 - Gene Bushuyev
1
@R Samuel Klatchko:好观点。当我写答案时,最初我打了“你不能访问 ab …”,后来改成了“你无法初始化…”,但没有确保句子的其余部分是合理的。帖子已编辑。 - In silico
1
@Gene Bushuyev:问题中原始代码中的类不是聚合体(存在非静态私有成员)。 - David Rodríguez - dribeas
此外:B(int a, int b) : A(a,b) { ; } - Jess
显示剩余3条评论

31

不考虑它们是 private 的事实,因为 abA 的成员,所以它们应该由 A 的构造函数初始化,而不是其他类(派生或非派生)的构造函数。

尝试:

class A
{
    int a, b;

protected: // or public:
    A(int a, int b): a(a), b(b) {}
};

class B : public A
{
    B() : A(0, 0) {}
};

19

不知何故,没有人列出最简单的方法:

class A
{
public:
    int a, b;
};

class B : public A
{
    B()
    {
        a = 0;
        b = 0;
    }

};

您无法在初始化列表中访问基类成员,但构造函数本身,就像任何其他成员方法一样,可以访问基类的publicprotected成员。


1
不错。这种做法有什么缺点吗? - Wander3r
2
@SaileshD:如果您正在使用昂贵的构造函数初始化对象,则可能存在这种情况。当分配B的实例时,它首先将被默认初始化,然后在B的构造函数内部进行赋值。但我也认为编译器仍然可以优化这个过程。 - Violet Giraffe
3
class A内部,我们不能依赖于ab已被初始化。例如,任何实现class C : public A的代码都可能忘记调用a=0;并留下未初始化的a - Sparkofska
1
当然,这不是初始化。"子类仍然可以重新初始化它们"只是无稽之谈,C++没有重新初始化的操作。 - Ben Voigt
1
一个可能的坏情况是,如果你的a或b是引用而不是普通变量,并且你想将它们初始化为指向某些变量,那么这只能在初始化列表中完成。但是一般来说,在简单的情况下,这确实是最优的。 - SF.
显示剩余3条评论

3
# include<stdio.h>
# include<iostream>
# include<conio.h>

using namespace std;

class Base{
    public:
        Base(int i, float f, double d): i(i), f(f), d(d)
        {
        }
    virtual void Show()=0;
    protected:
        int i;
        float f;
        double d;
};


class Derived: public Base{
    public:
        Derived(int i, float f, double d): Base( i, f, d)
        {
        }
        void Show()
        {
            cout<< "int i = "<<i<<endl<<"float f = "<<f<<endl <<"double d = "<<d<<endl;
        }
};

int main(){
    Base * b = new Derived(10, 1.2, 3.89);
    b->Show();
    return 0;
}

如果您想通过Derived类的构造函数调用进行接口推送这些值,同时希望初始化Derived类对象中存在的Base类数据成员,那么这是一个工作示例。


3

为什么你不能这样做?因为该语言不允许你在派生类的初始化列表中初始化基类成员。

如何完成此操作?可以按照以下方式实现:

class A
{
public:
    A(int a, int b) : a_(a), b_(b) {};
    int a_, b_;
};

class B : public A
{
public:
    B() : A(0,0) 
    {
    }
};

1
虽然这在极少数情况下是有用的(如果不是这种情况,语言将直接允许它),但请看基于成员的Base惯用法。这不是一个无代码的解决方案,您需要添加额外的继承层,但它可以完成工作。为了避免样板代码,您可以使用boost的实现

-1

聚合类(如您示例中的 A)必须将其成员设置为公共,并且不能有用户定义的构造函数。它们使用初始化列表进行初始化,例如 A a {0,0}; 或在您的情况下 B() : A({0,0}){}。基本聚合类的成员不能在派生类的构造函数中逐个初始化。

(*)要准确地说,正如正确提到的那样,原始的 class A 由于具有私有的非静态成员而不是聚合。


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