如何将初始化程序与rvalue引用参数一起使用//为什么不能用另一个C风格的数组变量初始化C风格的数组

时间:2017-12-07 13:16:40

标签: c++ arrays initializer-list rvalue

序言

在阅读本文之前,请注意我是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]()
  4. 辅助问题1 :为什么只有在将变量声明为类成员时,IntelliSense才允许显示这些重载? (如果在main()中声明

    ,则无法显示CTRL + SHIFT + SPACE)

    最吸引我的是那个带有右值参考的那个:

    • 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);
    }
    

2 个答案:

答案 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?