Как использовать инициализатор со ссылочным параметром rvalue // Почему невозможно инициализировать массив в стиле C с помощью другой переменной массива в стиле C

преамбула

Прежде чем читать это, учтите, что я новичок в C++. Я еще не изучил все (базовые) концепции (например, шаблоны), но я пытаюсь полностью понять некоторые основы, прежде чем перейти к чему-то другому.

Поэтому, пожалуйста, не упоминайте std::string или std:array, vector, boost::arrays и их превосходство над массивами в стиле C. Я уверен, что они есть, но это не главное здесь:)

Я также теперь, что иногда предпочтительнее назначать элементы в теле ctor, чем использовать его список инициализатора члена.

Мой вопрос

Рассматривая следующий пример. Когда я подошел к моменту, когда мне нужно было определить конструктор копирования для инициализации моего персонажа 2, объявленного в main(), я заметил, что в Visual Studio 2017 IntelliSense отображает 3 различных перегрузки для инициализации членов класса из списка инициализаторов. Например, для переменной name доступны следующие перечисленные перегрузки:

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

Вспомогательный вопрос 1. Почему IntelliSense позволяет отображать эти перегрузки только тогда, когда переменная объявлена ​​как член класса? (ничего не отображается с CTRL+SHIFT+SPACE, если объявлено в main())


Больше всего меня интересует тот, в котором есть ссылка на значение:

  • char [50](char [50] &&) для переменной name
  • int [10](int [10] &&) для переменной "данные"
  • int(int &&) для переменной 'singledata'

Теперь, если я использую name() в списке инициализатора, кажется, используется перегрузка #3. Если я использую name("aaa")перегрузка #1, похоже, будет использоваться. Это заставило меня исследовать другие концепции: теперь я понимаю концепции ссылок lvalue, rvalue и lvalue (но не это, слишком сложное для меня сейчас), и мне не удается полностью понять концепцию ссылок на rvalue и семантику перемещения,

Учитывая мое частичное понимание, я попытался объявить ссылку на rvalue в моем "параметризованном конструкторе 2" (который затем становится lvalue типа "ссылка на rvalue на int") и инициализировать с ней переменную "singledata", надеясь увидеть инициализатор Перегрузка #3 появляется в IntelliSense, но это не так. В этом случае снова используется тот, у которого есть параметр ссылки lvalue (перегрузка #2). Это я не могу объяснить.

Итак, я застрял, и это мой ГЛАВНЫЙ ВОПРОС 1: Когда инициализатор со ссылочным параметром rvalue используется для члена класса?


ОСНОВНОЙ ВОПРОС 2: Почему невозможно инициализировать массив символов, используя другой массив символов, в то время как это возможно с помощью строковых литеральных выражений? потому что существуют чистые значения r (значения), в то время как переменная может быть в лучшем случае преобразована только в выражение 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 слово перемещается на "ад", затем 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 ответа

Вопрос № 1:

Я полагаю, вы спрашиваете, почему ваш singledata инициализация не вызывает его инициализатор r-значения. Короткий ответ: потому что вы не передаете это r-значение.

В вашем конструкторе выше, параметр val1 имеет ссылочный тип r-значения. Однако следует помнить, что свойство r-valueness относится к выражениям, а не к типам. В инициализации singledata(val1)подвыражение val1 является l-значением, хотя переменная val1 имеет ссылочный тип r-значения. Это l-значение, как определено правилами, которые делают что-то l-значением. Кстати, правила - это буквально список условий, которые, если они выполняются, делают выражение l-значением. Грубо говоря, вы можете объяснить, что val1 здесь значение l, потому что это объект, адрес которого можно взять.

Вы хотели ссылочную версию r-значения инициализатора для singledata, вы бы использовали singledata(std::move(val1)) вместо. Здесь выражение std::move(val1) имеет тип R-значения, по определению того, что std::move делает. Ссылка на r-значение внутри singledata таким образом, конструктор может связываться с ним.

ОТВЕТ на Вспомогательный вопрос 1:

Потому что массивы, которые являются частью структур или классов, не разрушаются (например, когда вся структура или класс передается функции).

источник: http://www.learncpp.com/cpp-tutorial/6-8-pointers-and-arrays/

ОТВЕТ НА ГЛАВНЫЙ ВОПРОС 1:

Как указал Смихей, правильный путь, по-видимому, заключается в использовании singledata(std::move(val1)) в списке инициализатора члена. И я думаю, что Visual Studio (2017) просто не отображает правильную перегрузку, как показано здесь: std:: move строкового литерала - какой компилятор правильный?

Другие вопросы по тегам