繁体   English   中英

嵌套联合编译错误:C2280 试图引用已删除的 function

[英]Nested union compilation error: C2280 attempting to reference a deleted function

我对Clustering class 有疑问,其中的联合导致了问题。

我已经尝试了几件事,但仍然没有,检查了操作员,但无法找到错误。

这最初是使用Visual Studio 2008构建的,并且在那里编译得很好。
但是我必须用Visual Studio 2019来编译,编译时遇到了这个问题:

error C2280: 'Clustering<T>::NewType::~NewType(void)': attempting to reference a deleted function
error C2280:         with
error C2280:         [
error C2280:             T=ConsortiaRelationElem
error C2280:         ]

我想这个错误是由于 c++ 11 参数的实现造成的。

这是Clustering class 的定义:

template <typename T> 
class Clustering
{
    friend CPlayer;
public: 
    union NewType
    {  
        struct  
        {
            T _Ttype;
        };
        CPlayer* m_player;
    };

    NewType m_Data;
public:
    Clustering(){};
    Clustering(const T& _Ttype)
    { 
        m_Data.m_player = NULL;
        memcpy( &m_Data._Ttype, &_Ttype, sizeof(T) );
    };
    Clustering& operator=(const T& _Ttype)
    {
        m_Data.m_player = NULL;
        memcpy(&m_Data._Ttype, &_Ttype, sizeof(T));
    };
    ~Clustering() { };
};

这是我目前在编译时遇到的所有错误:

warning C4624: 'Clustering<T>::NewType': destructor was implicitly defined as deleted
warning C4624:         with
warning C4624:         [
warning C4624:             T=ConsortiaRelationElem
warning C4624:         ]

error C2280: 'Clustering<T>::NewType::~NewType(void)': attempting to reference a deleted function
error C2280:         with
error C2280:         [
error C2280:             T=ConsortiaRelationElem
error C2280:         ]
message : 'Clustering<T>::NewType::~NewType(void)': function was implicitly deleted because 'Clustering<T>::NewType' has a variant data member 'Clustering<T>::NewType::_Ttype' with a non-trivial destructor

1. 非平凡的工会成员

您的代码中的问题之一是联合。

只有当它们的成员对它们有微不足道的暗示时,联合才会隐含地定义它们的特殊成员函数。
-> 将成员添加到实现特殊成员 function 的联合后,您必须为您的联合手动实现该成员。

来自 c++ 标准:

最多一个联合的非静态数据成员可以有一个大括号或等号初始化器。 [注意:如果联合的任何非静态数据成员具有非平凡的默认构造函数 (12.1)、复制构造函数 (12.8)、移动构造函数 (12.8)、复制赋值运算符 (12.8)、移动赋值运算符 (12.8),或析构函数(12.4),联合的对应成员 function 必须是用户提供的,否则它将为联合隐式删除(8.4.3)。 ——尾注]

例子:

// only trivial members: the compiler will provide all functions for you
union A {
  int i;
  float f;
  double d;

  // member functions provided by your compiler
  A() = default;
  A(const A&) = default;
  A(A&&) = default;
  A& operator=(const A&) = default;
  A& operator=(A&&) = default;
  ~A() = default; 
};

// std::string & std::vector have non-trivial constructors, assignment operators and destructors
// so the member functions will be deleted.
union B {
  int i;
  std::string s;
  std::vector<int> v;

  // member functions provided by your compiler
  B() = delete;
  B(const B&) = delete;
  B(B&&) = delete;
  B& operator=(const B&) = delete;
  B& operator=(B&&) = delete;
  ~B() = delete; 
};

带有您的代码的godbolt示例

这背后的原因是编译器实际上无法生成这些函数。 例如, union B的构造函数需要知道它是否需要调用std::stringstd::vector的构造函数或根本不需要调用 - 但这取决于您初始化的编译器还不知道的成员。

union B的析构函数也是如此——它应该调用哪个析构函数? std::string::~string() std::vector<int>::~vector() 或者在 int 情况下根本没有? 工会不知道哪个成员是活跃的,所以没有办法提供一个合理的析构函数。

因此,编译器将实际实现所有这些特殊功能的责任留给您。

实现两种类型的泛型联合的一个非常简单的示例如下所示:


template<class T, class U>
struct variant {
private:
    enum {
        IS_NONE,
        IS_T,
        IS_U
    } activeMember;

    union {
        T t;
        U u;
        char dummy;
    };

public:
    variant() : activeMember(IS_NONE), dummy() {}
    variant(T const& _t): activeMember(IS_T), t(_t) {}
    variant(U const& _u): activeMember(IS_U), u(_u) {}
    variant(variant const& other): variant() { *this = other; }

    variant& operator=(variant const& other) {
        if(&other != this) {
            reset();
            activeMember = other.activeMember;
            if(other.activeMember == IS_T)
                new (&t) T(other.t);
            else if(other.activeMember == IS_U)
                new (&u) U(other.u);
        }
        return *this;
    }

    variant& operator=(T const& _t) {
        reset();
        activeMember = IS_T;
        new (&t) T(_t);
        return *this;
    }

    variant& operator=(U const& _u) {
        reset();
        activeMember = IS_U;
        new (&u) U(_u);
        return *this;
    }

    explicit operator T const&() {
        if(activeMember != IS_T) throw std::domain_error("variant is not T!");
        return t;
    }

    explicit operator U const&() {
        if(activeMember != IS_U) throw std::domain_error("variant is not U!");
        return u;
    }

    explicit operator bool() {
        return activeMember != IS_NONE;
    }

    ~variant() { reset(); }

    void reset() {
        if(activeMember == IS_T)
            t.~T();
        else if(activeMember == IS_U)
            u.~U();
        activeMember = IS_NONE;
    }
};

螺栓示例

可以这样使用:

variant<int, std::string> v = 12;
v = "MIAU";

请注意,我们需要在构造函数中初始化联合成员之一,并根据需要调用它们各自的析构函数。
如果我们想在构造后切换活跃的成员,我们还需要先破坏之前活跃的联合成员,然后使用placement-new构造新的值。

1.1 使用std::variant

如果您可以使用 C++17,我建议您使用std::variant而不是联合,因为这与我在上面发布的variant class 基本上做同样的事情。


2.非平凡类型的memcpy

你使用memcpy( &m_Data._Ttype, &_Ttype, sizeof(T) ); 在 class 的构造函数和赋值运算符中。

如果对象是可 简单复制的,则仅允许使用memcpy复制对象,即:

static_assert(std::is_trivially_copyable<T>::value, "T is not trivially copyable!");

鉴于您得到的编译错误,您的T很可能无法轻松复制,因此在这种情况下使用memcpy会导致未定义的行为

您可以改为使用placement new 来调用T的复制或移动构造函数,例如:

template<class T>
Clustering<T>& Clustering<T>::operator=(T const& t)
{
    new (&m_Data._Ttype) T(t);
    return *this;
};

也不要忘记在你的~Clustering()析构函数中为你的T调用析构函数(如果它不是很容易破坏的话)(否则你会泄漏你的T :):

template<class T>
Clustering<T>::~Clustering() {
    if(/* union actually contains T */)
        m_Data._Ttype.~T();
}

  1. 完整的工作示例

在您的示例中,您可以像这样编写Clustering

template <typename T> 
class Clustering
{
    friend CPlayer;
public: 
    union
    {
        T _Ttype;
        CPlayer* m_player;
    };
    bool containsT;
public:
    Clustering() : containsT(false), m_player(nullptr) {};
    Clustering(T const& t) : containsT(true), _Ttype(t) {};
    
    Clustering& operator=(T const& t)
    {
        // cleanup old T if needed
        if(containsT)
          _Ttype.~T();

        containsT = true;
        new (&_Ttype) T(t);

        return *this;
    };


    ~Clustering() {
        if(containsT)
            _Ttype.~T();
    };
};

工作神螺栓示例

(请注意,我们可以避免清理m_player ,因为指针是微不足道的。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM