16

在阅读了@Mehrdad最近提出的关于哪些类应设为不可移动因此不可复制的问题后,我开始想知道是否存在可以复制但不可移动的类的用例。从技术上讲,这是可能的:

struct S
{
    S() { }
    S(S const& s) { }
    S(S&&) = delete;
};

S foo()
{
    S s1;
    S s2(s1); // OK (copyable)
    return s1; // ERROR! (non-movable)
}

虽然S有一个复制构造函数,但它显然没有对CopyConstructible概念进行建模,因为这反过来又是对MoveConstructible概念的一种改进,它需要存在一个(未删除的)移动构造函数(参见第 17.6.3.1/2 节,表 21) .

S像上面这样的类型是否有任何用例,它是可复制但不可复制 CopyConstructible 且不可移动的?如果不是,为什么不禁止在同一个类中声明复制构造函数删除的移动构造函数?

4

5 回答 5

12

假设你有一个类,移动起来并不比复制便宜(也许它包含std::array一个 POD 类型)。

从功能上讲,您“应该”将其设为 MoveConstructible,使其S x = std::move(y);行为类似于S x = y;,这就是 CopyConstructible 是 MoveConstructible 的子概念的原因。通常,如果您根本没有声明任何构造函数,这“就可以工作”。

在实践中,我想您可能希望暂时禁用移动构造函数,以便通过移动. 对我来说,禁止这样做似乎有些过分。在完整的代码中强制执行良好的界面设计不是标准的工作:-)S

于 2013-01-14T17:11:05.130 回答
9

我目前不知道删除移动构造函数/赋值的用例。如果不小心完成,它将不必要地阻止类型从工厂函数返回或放入std::vector.

但是,删除的移动成员是合法的,以防万一有人发现它们的用途。打个比方,const&&多年来我都知道毫无用处。人们问我我们是否不应该直接取缔它。但最终在我们获得了足够的该功能经验后,确实出现了一些用例。删除的移动成员也可能发生同样的情况,但据我所知还没有。

于 2013-01-14T20:12:59.680 回答
3

我认为没有任何合理的课程可以阻止move,但允许copy。从同一个主题可以清楚地看出,当您不再需要原始对象时,移动只是一种有效的复制方式。

于 2013-01-14T17:09:54.637 回答
2

我今天正在研究这个问题,因为我们已经将一些代码从 VS2005 移植到 VS2010 并开始看到内存损坏。原来是因为优化(以避免在进行地图查找时复制)没有转化为具有移动语义的 C++11。

class CDeepCopy
{
protected:
    char* m_pStr;
    size_t m_length;

    void clone( size_t length, const char* pStr )
    {
        m_length = length;
        m_pStr = new char [m_length+1];
        for ( size_t i = 0; i < length; ++i )
        {
            m_pStr[i] = pStr[i];
        }
        m_pStr[length] = '\0';
    }

public:
    CDeepCopy() : m_pStr( nullptr ), m_length( 0 )
    {
    }

    CDeepCopy( const std::string& str )
    {
        clone( str.length(), str.c_str() );
    }

    CDeepCopy( const CDeepCopy& rhs )
    {
        clone( rhs.m_length, rhs.m_pStr );
    }

    CDeepCopy& operator=( const CDeepCopy& rhs )
    {
        if (this == &rhs)
            return *this;

        clone( rhs.m_length, rhs.m_pStr );
        return *this;
    }

    bool operator<( const CDeepCopy& rhs ) const
    {
        if (m_length < rhs.m_length)
            return true;
        else if (rhs.m_length < m_length)
            return false;

        return strcmp( m_pStr, rhs.m_pStr ) < 0;
    }

    virtual ~CDeepCopy()
    {
        delete [] m_pStr;
    }
};

class CShallowCopy : public CDeepCopy
{
public:

    CShallowCopy( const std::string& str ) : CDeepCopy()
    {
        m_pStr = const_cast<char*>(str.c_str());
        m_length = str.length();
    }

    ~CShallowCopy()
    {
        m_pStr = nullptr;
    }
};

int _tmain(int argc, _TCHAR* argv[])
{
    std::map<CDeepCopy, int> entries;
    std::string hello( "Hello" );

    CDeepCopy key( hello );
    entries[key] = 1;

    // Named variable - ok
    CShallowCopy key2( hello );
    entries[key2] = 2;

    // Unnamed variable - Oops, calls CDeepCopy( CDeepCopy&& )
    entries[ CShallowCopy( hello ) ] = 3;

    return 0;
}

上下文是我们希望在映射键已经存在的情况下避免不必要的堆分配 - 因此,CShallowCopy 类用于进行初始查找,然后如果这是插入,它将被复制。问题是这种方法不适用于移动语义。

于 2013-01-15T17:04:30.580 回答
0

这取决于您如何为您的类型定义移动操作的语义。如果移动仅仅意味着通过资源窃取来优化副本,那么答案可能是否定的。但是,如果移动意味着移动垃圾收集器或其他一些自定义内存管理方案所使用的“重定位”,那么答案可能是肯定的。

考虑一个位于特定街道地址上的房子的真实示例。可以将该房屋的副本定义为使用完全相同的蓝图建造的另一栋房屋,但位于另一个地址。在这些条件下,我们可以继续说房子不能移动,因为可能有人通过它的地址来指代它。转换为技术术语,对于具有入站指针的结构,移动操作可能是不可能的。

我可以想象一个信号/插槽库的有点扭曲的实现,它允许复制其信号对象,但不允许移动它们。

免责声明:一些 C++ 纯粹主义者会指出 STL(以及标准)定义了什么是移动操作,它与我在这里描述的不符,所以我不会对此争论。

于 2013-01-15T11:01:27.807 回答