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

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

我正在使用 VS2013 和 C++11。

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

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

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

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

我曾希望不需要这样做并且能够依赖隐式移动构造函数。否则,用户代码必须记住提供额外的构造函数和赋值似乎是一种负担。如果要求 DeepData2 肯定需要一个显式移动构造函数,如果用户代码忘记提供一个,我可以让它出错吗?无论如何检测模板类型是否由于具有显式移动构造函数的成员而需要显式移动构造函数?当我使用 std 类型特征时,它们似乎没有给我足够的信息来编写像 "hey user code, for template arg T you need move semantics and forgot"

这样的像样的断言

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

#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 编译器的主要缺失组件就是他们所说的 "expression SFINAE" 失败。

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