Как использовать инициализатор со ссылочным параметром rvalue // Почему невозможно инициализировать массив в стиле C с помощью другой переменной массива в стиле C
преамбула
Прежде чем читать это, учтите, что я новичок в C++. Я еще не изучил все (базовые) концепции (например, шаблоны), но я пытаюсь полностью понять некоторые основы, прежде чем перейти к чему-то другому.
Поэтому, пожалуйста, не упоминайте std::string или std:array, vector, boost::arrays и их превосходство над массивами в стиле C. Я уверен, что они есть, но это не главное здесь:)
Я также теперь, что иногда предпочтительнее назначать элементы в теле ctor, чем использовать его список инициализатора члена.
Мой вопрос
Рассматривая следующий пример. Когда я подошел к моменту, когда мне нужно было определить конструктор копирования для инициализации моего персонажа 2, объявленного в main(), я заметил, что в Visual Studio 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] &&)
для переменной nameint [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 строкового литерала - какой компилятор правильный?