使用与C ++标准允许的成员变量相同的构造函数参数名称来初始化成员变量吗?


76

我发现可以使用与下面的示例所示名称相同的构造函数参数来初始化成员变量。

#include <cstdio>
#include <vector>

class Blah {
    std::vector<int> vec;

public:
    Blah(std::vector<int> vec): vec(vec)
    {}

    void printVec() {

        for(unsigned int i=0; i<vec.size(); i++)
            printf("%i ", vec.at(i));

        printf("\n");
    }
};

int main() {

    std::vector<int> myVector(3);

    myVector.at(0) = 1;
    myVector.at(1) = 2;
    myVector.at(2) = 3;

    Blah blah(myVector);

    blah.printVec();

    return 0;
}

带参数的g ++ 4.4-Wall -Wextra -pedantic不会发出警告,并且可以正常工作。它也适用于clang ++。我想知道C ++标准怎么说?是否合法并保证始终有效?


1
好问题。实际上,我已经使用了很多这种“样式”。从未怀疑它是允许的。
2011年

Answers:


92

我想知道C ++标准怎么说?是否合法且始终有效?

是。那是完全合法的。完全符合标准。

Blah(std::vector<int> vec): vec(vec){}
                             ^   ^                           
                             |   |
                             |    this is the argument to the constructor
                             this is your member data

由于您要求在标准中提供参考,因此这里有一个示例。

§12.6.2/ 7

mem初始化程序的expression-list中的名称在为其指定了mem-initializer的构造函数的范围内进行评估。

[Example:
class X {
 int a;
 int b;
 int i;
 int j;
 public:
 const int& r;
  X(int i): r(a), b(i), i(i), j(this->i) {}
                      //^^^^ note this (added by Nawaz)
};

初始化X :: r以引用X :: a,使用构造函数参数i的值初始化X :: b,使用构造函数参数i的值初始化X :: i,并使用值初始化X :: j之X :: i; 每次创建X类对象时都会发生这种情况。]

[注意:由于mem-initializer在构造函数的范围内进行评估,因此可以在mem-initializer的expression-list中使用this指针来引用要初始化的对象。]

如您所见,在上面的示例中还有其他有趣的事情,以及来自标准本身的注释。


顺便说一句,顺便说一句,为什么不接受该参数作为const引用:

 Blah(const std::vector<int> & vec): vec(vec) {}
      ^^^^const              ^reference

它避免了不必要的原始矢量对象的复制。


快速解答,不过在标准文档(n3290)中我找不到初始化列表,哪一章?
尼尔斯(Nils)

3
在C ++ 0x中,最好按值接受它,然后将其移动到其目的地:Blah(std::vector<int> vec) : vec(std::move(vec)){}。您可以在C ++ 03这样的模拟这个:Blah(std::vector<int> vecSrc) { std::swap(vec, vecSrc); }
GManNickG

3
@Tomalak:大声笑,我仍然很惊讶您发现它不可读。这很简单,我想这很普通。
GManNickG 2011年

1
@GMan:我仍然将其归类为“技巧”。我知道它做什么,但它一眼它的代码不是很明显意味着,我想。您必须仔细考虑所有步骤,以了解原始原始对象没有被移动到任何地方。
在轨道进行比赛

2
尽管这是合法的,但这是不正确的做法。使用-Wshadow(或等效功能)时,它会使水变得混乱,并使您面临更多有害的编程错误。
里克

15

它可以保证始终有效(我经常使用它)。编译器知道初始化列表的形式为:member(value),因此它知道第一vecvec(vec)必须是成员。现在,在用于初始化成员的参数上,可以使用成员,构造函数的参数和其他符号,就像在构造函数中出现的任何表达式中一样。此时,它将应用常规查找规则,并且参数vec隐藏了member vec

该标准的第12.6.2节涉及初始化,并解释了第2段处理成员查找和第7段处理参数查找的过程。

mem初始化程序的expression-list中的名称在为其指定了mem-initializer的构造函数的范围内进行评估。[例:

class X {
   int a;
   int b;
   int i;
   int j;
public:
   const int& r;
   X(int i): r(a), b(i), i(i), j(this->i) {}
};

2
+1这里的关键点,这所有人都没有说出来:这个作品,因为同样的查找规则被应用为将在构造函数体内,可以使用-这是关键的一点-手段的说法隐藏/阴影的成员,这就是为什么它起作用的原因(对于“工作”一词的某种定义;任何隐藏/阴影都是不好的样式恕我直言)。
underscore_d

2

一个额外的反论点,或者也许只是要注意的一种情况是,使用move构造来初始化成员变量。

如果需要在构造函数的主体内使用成员变量,则需要通过this指针明确引用该成员变量,否则将使用处于未定义状态的移动变量。

template<typename B>
class A {
public:

  A(B&& b): b(std::forward(b)) {
    this->b.test(); // Correct
    b.test(); // Undefined behavior
  }

private:
  B b;
};

1

正如其他人已经回答的那样:是的,这是合法的。是的,这是标准保证的。

而且我每次看到它都感到可怕,迫使我停顿一下:“ vec(vec)?WTF?是的,vec成员变量...

这就是为什么许多人(包括我自己)喜欢使用命名约定的原因之一,该约定清楚地表明成员变量是成员变量。我看到的约定包括添加下划线后缀(vec_)或m_前缀(m_vec)。然后,初始化程序将读取:vec_(vec)/ m_vec(vec),这很容易。


在:x(y),y(y)中检测错误比在:m_x(in_y),m_y(in_y)中检测相同的错误要容易得多。装饰最少的名称可以减少干扰,使读者仅专注于基本内容,从而最终减少错误几率。标准中的这些规则并不是偶然选择的,它们是有目的的。
Dom
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.