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

Posted

技术标签:

【中文标题】解决由于具有可能已删除的默认构造函数的不变成员而导致的编译器错误【英文标题】:Resolving a compiler error due to an invariant member with a possible deleted default constructor 【发布时间】:2019-05-06 14:02:36 【问题描述】:

我已经按此顺序提出了一系列与相同源代码相关的问题:

    experimenting-with-unions-and-bitfields-within-a-structures-and-templates trying-to-flip-the-order-of-bits-in-stdbitset avoiding-ambiguity-in-overload-resolution

我在 Code Review 上也提出了一系列相关的问题。

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

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

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

我正在尝试将人们可以通过以下任何一种方式访问​​数据的想法纳入:

完整值:- 整个 64 位 qword 半值:- 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

然后我想测试以确保到目前为止的所有内容都可以轻松复制。所以我运行了这个小程序。

ma​​in.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

现在在查看 union Bits 时,它指出它是可简单复制的。因此,不要将结构中的 Bits 类型的变量声明为其数据成员;我相信我们应该能够在我们的结构中拥有一个匿名联合,这样我们就可以直接访问我们的 qword、dwords、单词和字节。所以现在这个类看起来像这样:

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”。然而;我收到此编译器错误 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::注册cpy

“vpc::Register”的默认构造函数不能被引用——它是一个被删除的函数

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

【问题讨论】:

可轻松复制!= 可轻松构造。 @S.M.我有两个 VS 实例都使用相同的代码运行,除了一个有Register 和另一个实例有MyRegister 我这样做只是为了确保将对象或变量类型声明为寄存器没有任何问题。我从一个实例复制了代码,但没有意识到它从另一个实例复制了编译器错误。除了RegisterMyRegister 的区别之外,其他一切都完全相同。我已经修复了错误消息以匹配它所属的代码。 【参考方案1】:

这是一个较短的复制:

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 实际上并不检查默认构造函数——它只是关于复制/移动构造函数/赋值。这是一个红鲱鱼。来看看default constructor rules:

如果满足以下条件,则将类 X 的默认默认构造函数定义为已删除:

X 是一个联合,它有一个变体成员和一个非平凡的默认构造函数,并且 X 的任何变体成员都没有一个默认成员初始化器, [...]

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

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

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

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

【讨论】:

【参考方案2】:

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

struct Register 
    Bits bits;
    Register() : value0
;

对于我的 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 ;
    

现在我的代码编译得很好,我得到了预期的输出。

【讨论】:

很高兴我的回答帮助您解决了问题。但这意味着你应该接受我的回答,而不是重复它。 @Barry 后续问题:***.com/q/56029329/1757805

以上是关于解决由于具有可能已删除的默认构造函数的不变成员而导致的编译器错误的主要内容,如果未能解决你的问题,请参考以下文章

了解 Lambda 闭包类型如何删除默认构造函数

隐藏实用程序类构造函数:实用程序类不应具有公共或默认构造函数

C++构造函数的default和delete

指向具有私有构造函数的类的类成员的指针

无法调用成员函数

详解c++中类的六个默认的成员函数