如何安全地在模板数据上使用 std::move()

How to safely use std::move() on template data

本文关键字:std move 数据 何安全 安全      更新时间:2023-10-16

我在VS2013和C++11工作。

我正在实现一个自定义模板化集合。当集合超出容量时,它会调整其存储大小。 此时,数据应从旧存储移动到新存储。

我真的很想在数据元素 T 上强制实施安全移动语义。 如果数据元素拥有资源,则应从原始存储中窃取资源的所有权并移动到新存储。 典型情况可能是指向数据数组或其他资源的字符串或指针。

我有几种数据类型具有显式移动构造函数和移动赋值运算符。 但是,如果那些具有显式移动构造函数(DeepData1)的类型本身是具有琐碎构造函数(DeepData2)的其他数据结构的成员,则会出现错误。 根据我阅读本文的方式,我希望我应该在 DeepData2 上获得一个隐式编译器生成的移动构造函数。http://en.cppreference.com/w/cpp/language/move_constructor

但是在下面的示例中,我展示了依赖 DeepData2 的隐式构造函数会由于指针_IMPORTANT_DATA上的双重删除而崩溃。 如果我显式使用 DeepData2 的移动构造函数,代码运行良好。

我希望不需要这样做,并且能够依赖隐式移动构造函数。另一方面,用户代码似乎必须记住提供额外的构造函数和赋值。如果要求 DeepData2 肯定需要一个显式移动构造函数,如果用户代码忘记提供一个,我会让它成为错误吗? 是否有办法检测模板类型是否需要显式移动构造函数,因为具有显式移动构造函数的成员? 当我使用 std 类型特征时,他们似乎没有给我足够的信息来写一个像样的断言,比如"嘿用户代码,对于模板参数 T,你需要移动语义并忘记"

这很复杂。感谢您的任何建议或帮助

#include "stdafx.h"
#include <vector>
#include <algorithm>
#include <iostream>

template <typename T>
class DeepVector
{
public:
    DeepVector()
    {
        deepCopyResize(4);
    }
    void push_back(T& v)
    {
        if (_capacity <= _count)
        {
            deepCopyResize(_capacity * 2);
        }
        // !! deep copy desired here !!
        _data[_count++] = std::move(v);
    }
    T& operator[](int i) { return _data[i];  }
    void deepCopyResize(int cap)
    {
        int n = std::min(_count, cap);
        T* d = new T[cap];
        if (_data)
        {
            for (int i = 0; i < n; ++i)
            {
                // !! deep copy desired here !!
                d[i] = std::move(_data[i]);
            }
            delete[] _data;
        }
        _data = d; 
        _capacity = cap; 
        _count = n;
    }
private:
    int _capacity = 0;
    int _count = 0;
    T* _data = nullptr; 
};

struct FlatData1
{
    int x = 0, y = 0; 
};
struct DeepData1
{
    DeepData1()
    {
    }
    DeepData1(int s)
    {
        _size = s;
        _IMPORTANT_DATA = new int[_size];
    }
    // move constructor
    DeepData1(DeepData1&& rhs)
    {
        _size = rhs._size;
        _IMPORTANT_DATA = rhs._IMPORTANT_DATA;  // pilfer
        rhs._size = 0;
        rhs._IMPORTANT_DATA = nullptr;
    }
    // move operator
    DeepData1& operator=(DeepData1&& rhs)
    {
        _size = rhs._size;
        _IMPORTANT_DATA = rhs._IMPORTANT_DATA; // pilfer
        rhs._size = 0;
        rhs._IMPORTANT_DATA = nullptr;
        return *this;
    }
    ~DeepData1()
    {
        if (_IMPORTANT_DATA)
        {
            std::cout << "non-trivial destructor" << std::endl;
            _size = 0; 
            // it is an error to delete important twice
            delete[] _IMPORTANT_DATA;
            _IMPORTANT_DATA = NULL;
        }
    }
    int _size = 0; 
    int* _IMPORTANT_DATA = nullptr; 
    void resize(int s)
    {
        delete[] _IMPORTANT_DATA; 
        _IMPORTANT_DATA = new int[s];
        _size = s;
    }
};
struct DeepData2
{
    int z = 0; 
    DeepData1 problem;     // this data does not deep copy implicitly ?
    //  DeepData2() {} 
//   despite C++ standard forcing default not supported by VS2013
//    DeepData2(DeepData2&&) = default;

//    DeepData2(int s) : problem(s) {}
    //!!!!!!!!!!!!!!!!!!!!!!!!!!!!
    // where are my implicit move constructors?
    // I have to uncomment these for the 
    // DeepData::operator=(DeepData&& rhs)
    // operator to be called 
    /*
     // have to manually implement move constructor?
     DeepData2(DeepData2&& rhs)
     {
     z = std::move(rhs.z);
     problem = std::move(rhs.problem);
     }
     // move operator
     DeepData2& operator=(DeepData2&& rhs)
     {
     z = std::move(rhs.z);
     problem = std::move(rhs.problem);
     return *this;
     }
   */
    // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!
};

int _tmain(int argc, _TCHAR* argv[])
{
    // ----------------------------------------------
    DeepVector<int> v1; 
    for (int i=0; i<5; ++i)
    { 
        v1.push_back(i);
    }
    if (v1[4] == 4)
    {
        std::cout << "resize 1 worked" << std::endl;
    }
    // ----------------------------------------------
    DeepVector<FlatData1> v2;
    for (int i = 0; i < 5; ++i)
    {
        v2.push_back(FlatData1());
        v2[i].x = i;
        v2[i].y = i;
    }
    if (v2[4].x == 4)
    {
        std::cout << "resize 2 worked" << std::endl;
    }
    // ----------------------------------------------
    DeepVector<DeepData1> v3;
    for (int i = 0; i < 5; ++i)
    {
        v3.push_back(DeepData1(10));
    }
    if (v3[4]._size == 10)
    {
        std::cout << "resize 3 worked" << std::endl;
    }

    // ----------------------------------------------

    bool b1 = std::is_move_constructible<DeepData1>();
    bool b2 = std::is_move_assignable<DeepData1>();
    bool b3 = std::is_trivially_move_assignable<DeepData1>();
    bool b4 = std::is_trivially_move_constructible<DeepData1>();
    bool b5 = std::is_move_constructible<DeepData2>();
    bool b6 = std::is_move_assignable<DeepData2>();
    // VS2013 says DeepData2 is trivially moveable with the implicit constructors
    bool b7 = std::is_trivially_move_assignable<DeepData2>();
    bool b8 = std::is_trivially_move_constructible<DeepData2>();
    DeepVector<DeepData2> v4;
    for (int i = 0; i < 5; ++i)
    {
        DeepData2 d2;
        d2.problem.resize(10);
        v4.push_back(d2); 
    }
    if (v4[4].problem._size == 10)
    {
        std::cout << "resize 4 worked" << std::endl;
    }

    return 0;
}
MSVC2013不支持

生成或=default移动构造函数(或赋值运算符)。

MSVC2015确实如此。 它的主要缺失组件是实际的C++11编译器,他们称之为"表达式SFINAE"失败。

如果不更换编译器,就无法在 MSVC2013 和 C++11 中工作。 您可以在 C++03 及其支持的 C++11 部分的混合中编程。