序言
在阅读这篇文章之前,请注意我是C++初学者。我还没有学会所有的(基本的)概念(例如模板),但我正在尝试完全理解一些基本面,然后再去做其他的事情。
因此,请不要提到std::string或std:array、vectors、boost::数组,以及它们相对于C样式数组的优越性。我相信他们是这样的,但这并不是重点:)
现在,与使用其成员初始化程序列表相比,有时更倾向于在ctor主体中分配元素。
我的问题
考虑下面的例子。当我必须定义一个复制构造函数来初始化main()中声明的character2时,我注意到在Visual 2017中,IntelliSense显示了3个不同的重载来从初始化程序列表中初始化类成员。例如,对于“name”变量,可用的列出的重载如下:
char [50](const char [50] &)char [50](char [50] &&)char [50]()附属问题1:为什么IntelliSense允许仅在变量声明为类成员时才显示这些重载?(如果CTRL+SHIFT+SPACE是在main()中声明的,则不会显示任何内容)
最让我感兴趣的是其中有参考价值的那个:
char [50](char [50] &&)int [10](int [10] &&)int(int &&)现在,如果在初始化程序列表中使用name(),则似乎使用了重载#3。如果我使用name("aaa"),则似乎使用了重载#1。这让我研究了其他概念:我现在理解了lvalue、rvalue和lvalue引用的概念(但不是这,现在对我来说太复杂了),而且我没有完全理解rvalue引用和移动语义的概念。
考虑到我的部分理解,我试图在我的“参数化构造函数2”中声明一个rvalue引用(然后它变成类型为"rvalue引用int“的lvalue ),并使用它初始化'singledata‘变量,希望看到初始化器重载#3在IntelliSense中弹出,但它没有。在这种情况下,带有lvalue引用参数(重载#2)的那个似乎再次被使用。我无法解释。
在这里,我被困住了,这是我的主要问题1:带rvalue引用参数的初始化器什么时候恰好被用于类成员?
主要问题2:为什么不能使用另一个char数组初始化char数组,而可以使用字符串文字表达式?因为存在纯xvalue (Prvalue),而变量最多只能转换为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指令集填充(实际上,在本例中:1 dword被移动为“地狱”,然后一个单词被移动到其余的),其中包含"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);
}发布于 2017-12-07 13:49:39
问题1:
我相信您是在问为什么您的singledata初始化没有调用它的r值初始化器。简短的回答:因为你没有传递给它一个r值。
在上面的构造函数中,参数val1具有r值引用类型.但是,请记住,“r值”属性适用于表达式而不是类型。在初始化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值引用可以绑定到它。
发布于 2017-12-15 14:55:24
对附属问题1:的答复
因为作为结构或类的一部分的数组不会衰减(例如,当整个结构或类被传递给一个函数时)。
来源:http://www.learncpp.com/cpp-tutorial/6-8-pointers-and-arrays/
对主要问题1:的答复
正如斯米希指出的那样,正确的方法似乎是在成员初始化程序列表中使用单数据(std::move(Val1))。我认为Visual (2017)只是没有显示正确的重载,如下所示:string文字的移动-哪个编译器是正确的?
https://stackoverflow.com/questions/47696006
复制相似问题