使用与 C++ 标准允许的成员变量相同的名称为构造函数参数初始化成员变量? [复制]

Posted

技术标签:

【中文标题】使用与 C++ 标准允许的成员变量相同的名称为构造函数参数初始化成员变量? [复制]【英文标题】:Initializing member variables using the same name for constructor arguments as for the member variables allowed by the C++ standard? [duplicate] 【发布时间】:2011-05-31 08:43:50 【问题描述】:

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

#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】:

我想知道 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-initializer 的表达式列表中的名称在指定 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, 用 的值初始化 X::b 构造函数参数 i,初始化 X::i 与构造函数的值 参数 i,并用 X::i 的值;这发生 每次 X 类的对象是 创建的。 ]

[注意:因为 mem-initializer 在 构造函数的范围,this 指针可用于 mem-initializer 的表达式列表 指代对象 初始化。 ]

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


顺便说一句,你为什么不接受参数作为 const 参考:

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

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

【讨论】:

感谢您的快速回答,但是在标准文档 (n3290) 中我找不到初始化列表,哪一章? 在 C++0x 中,最好按值接受它,然后将其移动到目的地:Blah(std::vector&lt;int&gt; vec) : vec(std::move(vec))。您可以像这样在 C++03 中进行模拟:Blah(std::vector&lt;int&gt; vecSrc) std::swap(vec, vecSrc); . @Tomalak:大声笑,我仍然很惊讶你发现它不可读。这很简单,而且我认为很常见。 @GMan:我仍然将其归类为“技巧”。我知道它的作用,但我认为,从代码中看它意味着并不明显。您必须仔细考虑这些步骤才能意识到原始 original 对象没有被移动到任何地方。 虽然是合法的,但这是不好的做法。使用 -Wshadow(或等效项)时,它会使水变得混乱,并使您面临更有害的编程错误。【参考方案2】:

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

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

mem-initializer 的表达式列表中的名称在指定 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) 
;

【讨论】:

+1 表示此处的关键点,其他人都没有说:这是有效的,因为应用了与 ctor 正文中使用的相同的查找规则,which - 和这是关键点 - 表示参数隐藏/隐藏成员,这就是为什么这样做(对于“工作”一词的某些定义;任何隐藏/隐藏都是糟糕的风格恕我直言)。【参考方案3】:

一个额外的反论点或者可能只是需要注意的一点是移动构造用于初始化成员变量的情况。

如果需要在构造函数体中使用成员变量,则需要通过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;
;

【讨论】:

【参考方案4】:

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

我每次看到它都觉得很可怕,迫使我停下来:“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) 中的相同错误更容易。最少修饰的名称可以减少干扰,让读者只关注基本内容,最终减少错误的可能性。标准中的这些规则不是偶然选择的,它们是有目的的。

以上是关于使用与 C++ 标准允许的成员变量相同的名称为构造函数参数初始化成员变量? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

C++ 构造函数 & 析构函数

构造函数

对象的构造与析构

表现为局部变量的 C++ 类成员

Java:类与继承

C++类里成员变量为什么加前缀m_?(避免成员变量与传入参数名称冲突)