代码之家  ›  专栏  ›  技术社区  ›  Curious

当存在用户定义的移动分配运算符时,删除模板化的移动分配运算符

  •  2
  • Curious  · 技术社区  · 8 年前

    我有一个具有复制构造函数的类,它只有在满足某个条件时才启用,例如,当类型参数不是引用时。以及既不可移动也不可复制的成员(比如互斥体)。 例如( https://wandbox.org/permlink/hRx51Ht1klYjN7v5 )

    #include <iostream>
    #include <tuple>
    #include <mutex>
    
    using std::cout;
    using std::endl;    
    
    template <typename T>
    class Something {
    public:
        Something() {}
    
        template <typename Type = T, 
                  std::enable_if_t<!std::is_reference<Type>{}>* = nullptr>
        Something(const Something&) {}
    
        Something& operator=(Something&&) {
            return *this;
        }
    
        std::mutex mutex_;
    };
    
    int main() {
        auto&& one = Something<int>{};
        auto two = one;
        std::ignore = two;
    }
    

    当我编译这段代码时,我得到一个错误

     copy constructor is implicitly deleted because 'Something<int>' has a user-declared move assignment operator
        Something& operator=(Something&&) {
                   ^
    1 error generated.
    

    好的,所以我尝试对移动分配运算符应用相同的约束

    template <typename T>
    class Something {
    public:
        Something() {}
    
        template <typename Type = T, 
                  std::enable_if_t<!std::is_reference<Type>{}>* = nullptr>
        Something(const Something&) {}
    
        template <typename Type = T, 
                  std::enable_if_t<!std::is_reference<Type>{}>* = nullptr>
        Something& operator=(Something&&) {
            return *this;
        }
    
        std::mutex mutex_;
    };
    

    错误更改为

    copy constructor of 'Something<int>' is implicitly deleted because field 'mutex_' has an inaccessible copy constructor
        std::mutex mutex_;
                   ^
    1 error generated.
    

    我能怎么做吗?当我明确希望默认构造不可移动的不可复制成员时,为什么编译器会抱怨呢?当我移除约束时,这编译得很好 https://wandbox.org/permlink/daqWAbF40MyfDJcN

    2 回复  |  直到 8 年前
        1
  •  2
  •   StoryTeller - Unslander Monica    8 年前

    特殊成员函数(复制/移动)通过其签名进行标识。当您试图对它们进行模板化时,您正在用定义模板 另一个 签名,因为模板参数是模板签名的一部分,而该函数与您试图禁止的复制/移动对象无关。

    为了完整起见,引用标准,强调我的:

    [class.copy]/2

    非模板构造函数 如果类x的第一个参数是x&类型、const x&类型、volatile x&类型或const volatile x&类型,并且没有其他参数,或者所有其他参数都有默认参数([dcl.fct.default]),则该类x是一个复制构造函数。

    在描述其他特别成员时也会出现类似的措辞。它们必须是非模板。

    所以您不会用sfinae影响这些函数。你不能用sfinae直接影响它们,因为编译器确保它们总是存在的。影响它们生成或缺少它们的唯一方法是从基类继承(有条件地,甚至是继承),或者拥有成员数据,这将导致它们被定义为已删除。


    假设两年内没有发生任何变化,在C++ 20中有可能将复制构造函数(或任何特殊成员)约束为 requires 条款:

    Something(const Something&) requires !std::is_reference_v<T> {}
    
        2
  •  2
  •   Richard Hodges    8 年前

    建议的解决方法(如果我正确理解了需求)是将复制/移动功能和同步推迟到基类(如果需要,可以是mixin):

    #include <iostream>
    #include <tuple>
    #include <mutex>
    
    using std::cout;
    using std::endl;    
    
    // A class for handling synchronisation
    struct Synchro
    {
        std::mutex mutex_;
    };
    
    // a class defining capabilities given a data type
    template<class T>
    struct DataManager : Synchro
    {
        DataManager() {}
    
        DataManager(DataManager const&);
        DataManager& operator=(DataManager const&);
        DataManager(DataManager&&);
        DataManager& operator=(DataManager&&);
    
    };
    
    // special treatment for references
    template<class Ref>
    struct DataManager<Ref&> : Synchro
    {
        DataManager() {}
    
        DataManager(DataManager const&) = delete;
        DataManager& operator=(DataManager const&) = delete;
        DataManager(DataManager&&) = delete;
        DataManager& operator=(DataManager&&) = delete;
    };
    
    // derive something from the specialised DataManager
    template <typename T>
    class Something : private DataManager<T>
    {
    public:
        Something() {}
    
    };
    
    int main() {
        auto&& one = Something<int>{};
        auto two = one;
        std::ignore = two;
    }
    

    第一条评论中的问题解答:

    谢谢!如果在基本上是移动构造函数(但带有模板)的对象中有一个模板函数,那么在数据管理器删除了移动构造函数的情况下,当移动某个对象的实例时。那会发生什么?

    将移动/复制功能延迟到基类的原因是,我们希望完全从 Something .

    是否可以将构造委托给DataManager的默认构造函数?

    是的

    从具有已删除构造函数的类继承会有什么影响?

    如果您没有在派生类中定义特殊的构造函数/析构函数(我们故意没有这样做),并且我们在派生类中没有定义数据对象,那么它与将复制/移动构造函数规则延迟到基类具有相同的效果。这不是完全正确的描述,但实际上发生了什么。

    它是否隐式删除派生类的相应构造函数?

    在这种情况下,是的。

    派生类构造函数/赋值运算符是否相应地被删除?

    在这种情况下是有效的。

    记住要定义基类中的所有数据对象。

    推荐文章