如何将初始值设定项与右值引用参数一起使用 // 为什么不能使用另一个 C 样式数组变量初始化 C 样式数组

How to use the initializer with an rvalue reference param // Why not possible to initialize a C-style array with another C-style array variable

本文关键字:数组 样式 初始化 变量 不能 一起 另一个 为什么 参数 引用      更新时间:2023-10-16

序言

在阅读本文之前,请注意我是一个C++初学者。我还没有学习所有(基本)概念(例如模板),但我正在尝试完全理解一些基础知识,然后再继续其他内容。

所以请不要提及 std::string 或 std:array、vectors、boost::arrays 以及它们对 C 样式数组的优越性。我确信有,但这不是这里的重点:)

我现在也更喜欢在 ctor 主体中分配元素,而不是使用其成员初始值设定项列表。

我的问题

考虑以下示例。 当我必须定义一个复制构造函数来初始化在main()中声明的character2时,我注意到,在Visual Studio 2017中,IntelliSense显示3种不同的重载来初始化初始值设定项列表中的类成员。例如,对于"name"变量,列出的可用重载为:

  1. char [50](const char [50] &)
  2. char [50](char [50] &&)
  3. char [50]()

附属问题 1:为什么 IntelliSense 只允许在变量声明为类成员时才显示这些重载?(如果在main()中声明,则 CTRL+SHIFT+空格不显示任何内容)


最让我感兴趣的是其中带有右值引用的那个:

  • "name"变量的char [50](char [50] &&)
  • "数据"变量的int [10](int [10] &&)
  • "单一数据"变量的int(int &&)

现在,如果我在初始值设定项列表中使用name(),则似乎使用了重载 #3。如果我使用name("aaa"),似乎使用了重载 #1。这让我开始研究其他概念:我现在理解了左值、右值和左值引用的概念(但不是这个,现在对我来说太复杂了),而且我无法完全理解右值引用和移动语义的概念。

考虑到我的部分理解,我试图在我的"参数化构造函数 2"中声明一个右值引用(然后成为"对 int 的右值引用"类型的左值)并用它初始化"singledata"变量,希望看到初始化项重载 #3 在 IntelliSense 中弹出,但它没有。在这种情况下,带有左值引用参数(重载 #2)的那个似乎再次使用。我无法解释。

所以我在这里,卡住了,这是我的主要问题 1:带有 rvalue 引用参数的初始值设定项何时恰好用于类成员?


主要问题 2:为什么无法使用另一个 char 数组初始化 char 数组,而可以使用字符串文字表达式? 因为有纯右值(prvalues),而变量充其量只能转换为xValue表达式?

我知道这绝对不是标准的,但是如果我看一下反汇编,字符数组的初始化非常简单:

2:     const char arrchar1[10]("hello");
01142771 A1 38 9B 14 01       mov         eax,dword ptr [string "hello" (01149B38h)]  
01142776 89 45 D8             mov         dword ptr [arrchar1],eax  
01142779 66 8B 0D 3C 9B 14 01 mov         cx,word ptr ds:[1149B3Ch]  
01142780 66 89 4D DC          mov         word ptr [ebp-24h],cx  
01142784 33 C0                xor         eax,eax  
01142786 89 45 DE             mov         dword ptr [ebp-22h],eax 

当前堆栈帧中的堆栈分配空间[arrchar1](在我的情况下是[ebp-28h])只是由一个MOV指令集填充(2,实际上,在这种情况下:1 个 dword 被移动为"hell",然后 1 个单词被移动用于其余的)静态空间的内容包含"hello"字符串文字。

为什么没有C++方法在编译后产生类似的东西,它会执行同样的事情,但"移动"堆栈分配的空间(另一个数组变量)的内容,而不是"移动"一些静态内存?我期待这样的事情:char arrchar2[10](arrchar1)


例:

#include <string.h> // for strcpy
class Character
{
public:
//default constructor
Character() {};
//parameterized constructor 1
Character(const char * pname) : // pointer to the string literal must be const (otherwise it is Undefined Behaviour)
name(), data{ 1, 2, 3 } // mem-initializer-list: 
// - name is initialized with value-initialization (empty expression-list in a pair of parentheses following identifier)
// - (C++11) data is initialized using list-initialization which becomes aggregate-initialization since it is an aggregate (array)
{
strcpy_s(name, pname);
};
//parameterized constructor 2
Character(const char * pname, int &&val1) :
name(), data{ 1, 2, 3 }, singledata(val1)
{
strcpy_s(name, pname);
};
//copy constructor
Character(const Character & tocopy)
// member initializer list
//:name(),  // >> IntelliSense shows 3 initializer overloads: char [50](const char [50] &) || char [50](char [50] &&) || char [50]()
// >> (visible only if the array is declared in a class, no pop up in main...)
: name("aaa"),
//data()    // >> IntelliSense shows 3 initializer overloads: int [10](const int [10] &) || int [10](int [10] &&) || int [10]()
// >> (visible only if the array is declared in a class, no pop up in main...)
data{ 1, 2, 3 }
{
// ctor body definition
};
private:
char name[50];
int data[10];
int singledata;
};
void main()
{
Character character1("characterOne"); // the string literal has static storage duration (static memory), passed pointer allocated on stack memory
Character character2(character1);
Character character3("characterThree", 3);
}

问题 1:

我相信你在问为什么你的singledata初始化没有调用它的 r 值初始化器。简短的回答:因为你没有向它传递 r 值。

在上面的构造函数中,参数val1具有 r 值引用类型。但是,请记住,"r-valueness"属性适用于表达式而不是类型。在初始化singledata(val1)中,子表达式val1是一个 l 值,即使变量val1具有 r 值引用类型。它是一个 l 值,由使某物成为 l 值的规则定义。顺便说一下,规则实际上只是一个条件列表,如果满足这些条件,则表达式将成为 l 值。粗略地说,你可以解释val1在这里是一个l值,因为它是一个可以获取地址的对象。

如果您想要singledata的初始值设定项的 r 值引用版本,您将改用singledata(std::move(val1))。这里的表达式std::move(val1)具有 r 值类型,根据std::move所执行的定义。因此,singledata构造函数中的 r 值引用可以绑定到它。

对附属问题 1 的回答:

因为作为结构或类一部分的数组不会衰减(例如,当整个结构或类传递给函数时)。

来源: http://www.learncpp.com/cpp-tutorial/6-8-pointers-and-arrays/

对主要问题1的回答:

正如Smeehey指出的那样,正确的方法似乎是在成员初始值设定项列表中使用singledata(std::move(val1))。我认为Visual Studio(2017)只是没有显示正确的重载,如下所示:标准::字符串文字的移动 - 哪个编译器是正确的?