Устранение ошибки компилятора из-за инвариантного члена с возможным удаленным конструктором по умолчанию
Я задал ряд вопросов, которые относятся к одному и тому же исходному коду в следующем порядке:
Я также задавал эти серии вопросов в Code Review, которые также связаны между собой.
Это должно дать вам обзор моего оригинального дизайна кода, и это там для справочной и справочной информации. С тех пор я начал смотреть на свой рабочий код и хотел еще больше упростить его.
Затем я решил убрать специализации шаблонов и сделать так, чтобы мой класс Register имел ширину 64 бита вместо 8 бит по умолчанию, в то время как я специализировал регистры размера более высокого порядка.
Я пытаюсь воплотить идею, что можно получить доступ к данным любым из этих способов:
- Полное значение: - все 64-битное слово
- Половина значения: - 2 отдельных 32-битных слова
- Квартальное значение (я) - 4 отдельных 16-битных слова
- Восьмое значение (я) - 8 отдельных 8-битных байтов
И через утилиту использования std::bitset
можно легко получить доступ к любому из битов в полном 64-битном регистре. С использованием объединений я должен быть в состоянии правильно отобразить память так, чтобы регистр мог быть представлен и доступен любой из этих комбинаций:
std::bitset<64> qword
std::bitset<32> dword[2]
std::bitset<16> word[4]
std::bitset<8> byte[8]
Концепция использования объединения состоит в том, чтобы в памяти было одно пространство, представляющее 64 бита для любого заданного регистра. Теперь я пытаюсь сохранить свой класс Register тривиально копируемым.
Поэтому я изменил исходный код из того, что можно найти по ссылкам выше, до более простой версии этого:
Register.h
#include <algorithm>
#include <bitset>
#include <string>
#include <vector>
namespace vpc {
typedef std::int8_t i8;
typedef std::int16_t i16;
typedef std::int32_t i32;
typedef std::int64_t i64;
const std::uint16_t BYTE = 0x08;
const std::uint16_t WORD = 0x10;
const std::uint16_t DWORD = 0x20;
const std::uint16_t QWORD = 0x40;
typedef std::bitset<BYTE> Byte;
typedef std::bitset<WORD> Word;
typedef std::bitset<DWORD> DWord;
typedef std::bitset<QWORD> QWord;
union Bits {
QWord value;
DWord dword[2];
Word word[4];
Byte byte[8];
};
struct Register {
Bits bits;
Register() = default;
};
} // namespace vpc
Затем я хочу проверить, чтобы убедиться, что все до сих пор легко копируется. Итак, я запускаю эту короткую программу.
main.cpp
#include <iostream>
#include <type_traits>
#include "Register.h"
int main() {
using namespace vpc;
std::cout << std::boolalpha;
std::cout << "std::bitset<64> is trivially copyable "
<< std::is_trivially_copyable<std::bitset<64>>::value << '\n'
<< "QWord is trivially copyable "
<< std::is_trivially_copyable<QWord>::value << '\n'
<< "DWord is trivially copyable "
<< std::is_trivially_copyable<DWord>::value << '\n'
<< "Word is trivially copyable "
<< std::is_trivially_copyable<Word>::value << '\n'
<< "Byte is trivially copyable "
<< std::is_trivially_copyable<Byte>::value << '\n'
<< "Bits is trivially copyable "
<< std::is_trivially_copyable<Bits>::value << '\n'
<< "Register is trivially copyable "
<< std::is_trivially_copyable<Register>::value << '\n';
return EXIT_SUCCESS;
}
И я получаю этот вывод:
std::bitset<64> is trivially copyable true
QWord is trivially copyable true
DWord is trivially copyable true
Word is trivially copyable true
Byte is trivially copyable true
Bits is trivially copyable true
My Register is trivially copyable true
Теперь, глядя на объединение битов, он заявляет, что его легко копировать. Таким образом, вместо объявления переменной типа Bits в структуре в качестве члена данных; Я считаю, что мы должны иметь возможность иметь анонимный союз в нашей структуре, чтобы мы могли получить право на наши слова, слова, слова и байты напрямую. Так что теперь класс будет выглядеть так:
struct Register {
union {
QWord value;
DWord dword[2];
Word word[4];
Byte byte[8];
};
Register() = default;
};
Затем я запускаю эту строку кода в нашем main.cpp
// code...
std::cout << std::boolalpha;
std::cout << "Register is trivially copyable "
<< std::is_trivially_copyable<Register>::value << '\n';
// code...
И я получаю этот вывод:
Register is trivially copyable true
Хорошо, пока все хорошо.
Сейчас я работаю над своей функцией, которая работает с объектом Register. Это обратит порядок битов, как видно из ранее заданных вопросов. За исключением этого случая я не использую шаблоны. Итак, здесь я объявляю прототип функции в Register.h
после моего класса:
Register reverseBitOrder( Register& reg, bool copy = false );
И тогда я создал Register.cpp
файл только для реализации этой функции.
Register.cpp
#include "Register.h"
namespace vpc {
Register reverseBitOrder(Register& reg, bool copy) {
auto str = reg.value.to_string();
std::reverse(str.begin(), str.end());
if (copy) { // return a copy
Register cpy;
cpy.value = QWord(str);
return cpy;
} else {
reg.bits.value = QWord(str);
return { 0 };
}
}
} // namespace vpc
Итак, теперь, когда у меня написана функция, я очищаю свое решение и пытаюсь скомпилировать "Register.h". Тем не мение; Я получаю эту ошибку компилятора для Visual Studio 2017 с настройкой языка на последний черновой стандарт или флаг (/std:c++latest)
,
--- Build started: Project: Corgi64, Configuration: Debug Win32 ------
1>Register.cpp
1>c:\***\register.cpp(10): error C2280: 'vpc::Register::Register(void)': attempting to reference a deleted function
1>c:\***\register.h(40): note: see declaration of 'vpc::Register::Register'
1>c:\***\register.h(40): note: 'vpc::Register::Register(void)': function was implicitly deleted because 'vpc::Register' has a variant data member 'vpc::Register::value' with a non-trivial default constructor
1>c:\***\register.h(34): note: see declaration of 'vpc::Register::value'
1>Done building project "Corgi64.vcxproj" -- FAILED.
========== Build: 0 succeeded, 1 failed, 0 up-to-date, 0 skipped ==========
Поэтому, когда я нажимаю на ошибку C2280, она направляет меня к объявлению моей переменной Register cpy
, Когда я перемещаю курсор мыши поверх переменной cpy
это дает мне сообщение:
vpc:: Регистрация cpy
на конструктор по умолчанию "vpc::Register" нельзя ссылаться - это удаленная функция
Поэтому мой вопрос звучит так: почему конструктор по умолчанию удаляется, если все ранее было легко копируемым? Поскольку я теперь использую его внутри функции, неожиданно утверждается, что в моей структуре есть инвариантный член, который не имеет тривиально копируемого конструктора, и он указывает на Register::value
как виновник. Что вызывает это, как и почему? Что я могу сделать, чтобы это исправить или решить?
2 ответа
Вот короткое воспроизведение:
struct T {
T() { }
};
union X {
T t;
};
static_assert(std::is_trivially_copyable_v<T>); // fine
static_assert(std::is_trivially_copyable_v<X>); // fine
X x; // error: use of deleted function X::X()
Тривиально копируемое требование на самом деле не проверяет конструктор по умолчанию - это просто конструктор / назначение копирования / перемещения. Это красная сельдь здесь. Давайте посмотрим на правила конструктора по умолчанию:
По умолчанию конструктор по умолчанию для класса X определяется как удаленный, если:
- X - это объединение, в котором есть вариантный член с нетривиальным конструктором по умолчанию, и ни у одного вариантного члена X нет инициализатора элемента по умолчанию,
- [...]
В нашем X
, у нас есть вариант члена с нетривиальным конструктором по умолчанию (T()
предоставляется пользователем, следовательно, это нетривиально... и std::bitset
конструктор по умолчанию действительно что-то делает), поэтому конструктор по умолчанию определяется как удаленный. В этом примере конструктор по умолчанию неявно по умолчанию - в OP это явно по умолчанию - но эффект тот же.
Обходной путь должен предоставить конструктор по умолчанию, который делает... все, что вы хотите, чтобы конструктор по умолчанию фактически делал:
union X {
X() : t() { }
T t;
};
Эмпирическое правило здесь таково, что union
специальные члены предоставляются неявно, только если все варианты тривиальны.
После прочтения того, что сказал пользователь Барри в своем ответе, и я вернулся, чтобы посмотреть свой код, я смог сделать это:
struct Register {
Bits bits;
Register() : value{0}{}
};
для моего класса Register, и я изменил свое определение функции на это:
MyRegister reverseBitOrder(MyRegister& reg, bool copy) {
auto str = reg.value.to_string();
std::reverse(str.begin(), str.end());
if (copy) { // return a copy
MyRegister cpy;
cpy.value = QWord(str);
return cpy;
} else {
reg.value = QWord(str);
return {};
}
}
И теперь мой код компилируется нормально, и я получаю ожидаемые результаты.