解决由于具有可能已删除的默认构造函数的不变成员而导致的编译器错误

Resolving a compiler error due to an invariant member with a possible deleted default constructor

我问了一系列问题,这些问题都按顺序与同一个源代码相关:

我也在 Code Review 上问过这一系列的问题,它们也是相关的。

  1. emulating-virtual-registers-by-experimenting-with-unions-bitfields-structs-and-template-specialization
  2. emulating-virtual-registers-part-2

这应该让您大致了解我的原始代码设计,它可以作为参考和背景信息。从那时起,我开始查看我的工作代码并希望进一步简化它。

然后我决定删除模板特化,并让我的寄存器 class 默认为 64 位宽而不是 8 位,同时专门化高阶大小的寄存器。

我正在尝试将可以通过以下任何一种方式访问​​数据的想法结合起来:

并且通过 std::bitset 的实用程序使用,可以轻松访问完整 64 位寄存器中的任何位。通过使用联合,我应该能够正确映射内存,以便寄存器可以表示为以下任何组合并可以通过以下组合访问:

使用联合的概念是在内存中有一个 space 表示任何给定寄存器的 64 位。现在,我正努力使我的寄存器 class 易于复制。

所以我将上面链接中的原始代码修改为更简单的版本:

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 类型的变量作为它的数据成员;我相信我们应该能够在我们的结构中有一个匿名联合,这样我们就可以直接访问我们的 qword、dword、words 和 bytes。所以现在 class 看起来像这样:

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 对象进行操作的函数。它将反转位的顺序,如从先前提出的问题中看到的那样。除了在这种情况下我没有使用模板。所以在这里我在 class:

之后在 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"。然而;我收到此编译器错误 frpm 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 时,它会将我引导至我的注册变量 cpy 的声明。当我将鼠标光标移到变量 cpy 上方时,它会给我消息:

vpc::Register cpy

the default constructor of "vpc::Register" cannot be referenced -- it is a deleted function

所以我的问题变成了:如果之前的所有内容都可以轻松复制,为什么要删除默认构造函数?因为我现在在一个函数中使用它,突然间,它表明我的结构有一个不变的成员,它没有一个普通的可复制构造函数,它指向 Register::value 作为罪魁祸首。是什么原因造成的,如何以及为什么?我能做些什么来修复或解决这个问题?

这是一个较短的复制品:

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()

trivially copyable requirement doesn't actually check the default constructor - it's just about the copy/move constructor/assignment. That's a red herring here. Let's look at the default constructor rules:

A defaulted default constructor for class X is defined as deleted if:

  • X is a union that has a variant member with a non-trivial default constructor and no variant member of X has a default member initializer,
  • [...]

在我们的 X 中,我们有一个具有非平凡默认构造函数的变体成员(T() 是用户提供的,因此它是非平凡的......并且 std::bitset' s 的默认构造函数实际上做了一些事情),因此默认构造函数被定义为已删除。在这个例子中,默认构造函数是隐式默认的——在 OP 中它是显式默认的——但效果是一样的。

解决方法是提供一个默认构造函数来做...无论您希望默认构造函数实际做什么:

union X {
    X() : t() { }
    T t;
};

这里的经验法则是 union 特殊成员只有在所有变体都是微不足道的情况下才会隐式提供。

在阅读了用户 Barry 在他的回答中所说的内容后,我回去查看我的代码,我能够想出这个:

struct Register {
    Bits bits;
    Register() : value{0}{}
};

对于我的寄存器 class,我将我的函数定义更改为:

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 {};
    }
}

现在我的代码可以正常编译,并且得到了预期的输出。