【问题标题】:Usage of parameter and member variable in constructor构造函数中参数和成员变量的使用
【发布时间】:2014-02-07 11:10:52
【问题描述】:

在编写类的构造函数时,我经常问自己应该使用初始化的成员变量还是构造函数参数。这里有两个例子来说明我的意思:

构造函数参数

class Foo {
public:
    Foo(int speed) :
        mSpeed(speed),
        mEntity(speed)
    { }

private:
    int mSpeed;
    Entity mEntity;
}

成员变量

class Foo {
public:
    Foo(int speed) :
        mSpeed(speed),
        mEntity(mSpeed)
    { }

private:
    int mSpeed;
    Entity mEntity;
}

更多在构造函数主体中使用变量也会出现同样的问题。

构造函数参数

class Foo {
public:
    Foo(int speed) :
        mSpeed(speed)
    {
        mMonster.setSpeed(speed);
    }

private:
    int mSpeed;
    Monster mMonster;
}

成员变量

class Foo {
public:
    Foo(int speed) :
        mSpeed(speed)
    {
        mMonster.setSpeed(mSpeed);
    }

private:
    int mSpeed;
    Monster mMonster;
}

我知道这并不重要(除了一些特殊情况),这就是为什么我宁愿要求 cmets 进行代码设计,而不是什么使它起作用,什么不起作用。

如果您需要处理一个特定问题:哪种方式可以产生良好且一致的代码设计,并且其中一种方式比另一种方式具有(不利)优势?

编辑:不要忘记问题的第二部分。构造函数体中的变量呢?

【问题讨论】:

  • 根据经验:最好使用已初始化的成员!
  • ImportantFunctionmSpeed 有副作用吗?
  • 不,它只是一个“随机”函数,不能在初始化列表中调用。
  • 有点相关:另外,不要忘记在你的第一个 sn-p 的情况下,这些成员在类中声明的顺序将决定它们的初始化顺序; 不是初始化列表中的顺序。如果mEntity 在非静态成员列表中的mSpeed 之前声明,您将有一个不确定的值用作mEntity 的初始值设定项,即使初始值设定项列表中的顺序另有说明。
  • @Lukas 如果它与初始化无关,为什么要在构造函数中调用它?它会影响全局变量吗?它会进行初始化后吗?

标签: c++


【解决方案1】:

我会使用构造函数参数,因为在使用该初始化程序时,这些初始化程序的执行顺序取决于成员的声明顺序,而不是它们的列出顺序。所以,这里要小心。

【讨论】:

  • 初始化顺序已明确定义,并非由编译器“决定”。
  • 编译器不会“决定”。它由标准规定为类中成员声明的顺序。这根本不是随机的。
  • 上次我使用初始化程序时,声明成员的顺序对初始化程序的执行没有影响。这就是为什么我避免使用它们。但是如果你说有一个关于这个特定事物的标准并且它不是决定它的编译器,那么我会相应地编辑我的答案。更好的pmr?
  • C++11 12.6.2 [class.base.init] 说明了这一点。如果您想了解更多信息,请see this answer 回答相关问题。如果您的编译器从任何时候都没有遵循这一点,请庆幸您不再使用它,因为这对于破坏的正常工作至关重要(其中事情完全按照声明的 reverse 顺序从最后一个开始-成功构造的成员并向后移动)。
【解决方案2】:

我个人更喜欢使用构造函数参数,以避免使用尚未初始化的成员变量。

确实,在这个例子中:

class Foo {
private:
    int mEntity;
    int mSpeed;
public:
    Foo(int speed) :
        mSpeed(speed),
        mEntity(mSpeed)
    { }
}

mEntity 的初始化会发生在 mSpeed 的初始化之前(因为它是在之前声明的)。因此,您将使用未初始化的 mSpeed 初始化 mEntity。

--

在构造函数体本身内部,我也会使用构造函数参数,因为在调试时可以更直接地看到您使用 speed 来初始化 mMonster 和不是 mSpeed,它本身是用 speed 初始化的。当然这是一个极简的开销,但我们可以很容易地避免它,我认为这样做会更好。

【讨论】:

  • 是的,我也是这么想的。构造函数体中的变量呢?
  • 我也会使用构造函数参数(有关详细信息,请参阅我的编辑)
  • 为了避免您正在解决的问题 gcc 会发出警告:gcc compiler warning: "will be initialized after/when initialized here" 因此,如果您在 mSpeed 之后初始化 mEntity,当它需要以相反的顺序执行时,您会受到警告的珍视。只需将警告变为错误即可避免此特定问题。
【解决方案3】:

对于必须钳位参数的情况,我更喜欢使用成员变量:

class Foo {
public:
    Foo(int speed) :
        mSpeed((speed < 0 ? 0 : speed)),
        mEntity(mSpeed)
    { }
}

这样,如果参数无效,则不会导致后续成员也无效。

否则我坚持参数变量。

【讨论】:

    【解决方案4】:

    我会使用构造函数参数。为什么?因为这个问题。 construtor 参数清晰易读,你不需要对 C++ 了解很多就知道会发生什么。如果您对 C++ 了解不够,使用成员很容易出错,并且即使您做得对,也会让团队中没有您知识水平的其他人感到困惑。

    如有疑问,请保持简单。

    【讨论】:

      【解决方案5】:

      您绝对应该使用构造函数参数。如前所述,成员变量将按照它们在头文件中声明的顺序进行初始化,而不是按照它们在初始化列表中出现的顺序。

      如果订单不匹配,一些编译器会警告您,但使用构造函数参数只会让您少担心一件事。例如,当您编辑类界面时,很容易以这种方式引入错误。使用成员变量初始化其他成员变量并没有什么好处。

      【讨论】:

        【解决方案6】:

        我也会使用构造函数参数。 看简单的例子:

        // foo.h
        
        class Foo
        {
            std::unique_ptr<int[]> mBuff;
            int mSize;
        public:
        explicit    Foo(int size);
           // other methods...
        };
        
        // foo.c
        Foo::Foo(int size)
          : mSize(size) 
          , mBuff( std::make_unique<int[]>(size) ) // here using mSize is wrong, 
                    // because, mSize is not initialized yet.
                    // here mSize initialized after mBuff, because it's declarated after mBuff member.
        {}
        

        所以如果你使用成员而不是构造函数参数,你很容易造成错误情况。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-03-30
          • 2011-09-13
          • 2018-02-22
          • 2016-11-15
          • 2020-07-15
          • 2011-08-08
          • 1970-01-01
          相关资源
          最近更新 更多