序言
在阅读本文之前,请注意我是C ++初学者。我还没有学过所有(基本)概念(例如模板),但我正在尝试完全理解一些基础知识,然后继续学习其他东西。
所以请不要提到std :: string或std:array,vectors,boost :: arrays,以及它们对C风格数组的优越性。我相信他们是,但这不是重点:)
我现在也有时优先在ctor体中分配元素而不是使用其成员初始化列表。
我的问题
考虑以下示例。 当我到达那一刻我必须定义一个复制构造函数来初始化我在main()中声明的character2时,我注意到,在Visual Studio 2017中,IntelliSense显示3个不同的重载来初始化初始化列表中的类成员。例如,使用'name'变量,可用的列出的重载是:
char [50](const char [50] &)
char [50](char [50] &&)
char [50]()
辅助问题1 :为什么只有在将变量声明为类成员时,IntelliSense才允许显示这些重载? (如果在main()
中声明
最吸引我的是那个带有右值参考的那个:
char [50](char [50] &&)
代表'name'变量int [10](int [10] &&)
代表“数据”变量int(int &&)
代表'singledata'变量现在,如果我在初始化列表中使用name()
,则似乎使用了重载#3。如果我使用name("aaa")
,则似乎使用了重载#1。这让我对其他概念进行了研究:我现在理解了左值,右值和右值引用的概念(但不是this,对我来说太复杂了),我无法完全理解rvalue的概念引用和移动语义。
考虑到我的部分理解,我试图在我的“参数化构造函数2”中声明一个rvalue引用(然后它变成了“rvalue reference to int”类型的左值)并用它初始化'singledata'变量,希望能看到IntelliSense中弹出的初始化程序重载#3,但它没有。在这种情况下,具有左值参考参数(过载#2)的那个似乎再次被使用。我无法解释。
所以我在这里,卡住了,这是我的主要问题1 :什么时候带有右值参考参数的初始化程序恰好用于类成员?
主要问题2 :为什么使用字符串文字表达式可能无法使用另一个char数组初始化char数组? 因为有纯rvalues(prvalues)而变量只能转换为xvalue表达式?
我知道这绝对不是标准的,但是如果我看一下反汇编,chars数组的初始化非常简单:
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);
}
答案 0 :(得分:1)
问题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 :(得分:0)
回答辅助问题1:
因为作为结构或类的一部分的数组不会衰减(例如,当整个结构或类传递给函数时)。
来源:http://www.learncpp.com/cpp-tutorial/6-8-pointers-and-arrays/
回答主要问题1:
正如Smeehey指出的那样,正确的方法似乎是在成员初始化列表中使用singledata(std :: move(val1))。我认为Visual Studio(2017)只是没有显示正确的重载,如下所示:std::move of string literal - which compiler is correct?