是否有可复制但不可移动的类的用例?

时间:2013-01-14 17:04:14

标签: c++ c++11 copy-constructor move-semantics deleted-functions

在@Mehrdad阅读this recent question后,应该将哪些类不可移动,因此不可复制,我开始想知道是否有类的用例可以复制但不能移动。从技术上讲,这是可能的:

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 且不可移动?如果没有,为什么不禁止在同一个类中声明复制构造函数删除的移动构造函数?

5 个答案:

答案 0 :(得分:11)

假设你有一个移动成本比复制成本低的类(可能它包含一个POD类型的std::array)。

从功能上讲,你“应该”使其成为MoveConstructible,以便S x = std::move(y);表现得像S x = y;,这就是为什么CopyConstructible是MoveConstructible的子概念。通常,如果你根本没有声明构造函数,那么“只是工作”。

实际上,我认为您可能希望暂时禁用移动构造函数,以便检测程序中是否存在出现的效率高于它的代码确实是,通过移动S的实例。对我而言,禁止这样做似乎过分了。在完成的代码中强制执行良好的界面设计并不是标准的工作: - )

答案 1 :(得分:8)

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

然而,删除的移动成员是合法的,以防万一有人可能会找到它们的用途。作为类比,我知道const&&多年没用了。人们问我,我们是否应该禁止它。但是在我们获得足够的功能经验之后,最终会出现一些用例。删除的移动成员也会发生相同的 ,但据我所知还没有。

答案 2 :(得分:2)

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

答案 3 :(得分: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类用于执行初始查找,然后如果这是插入则将被复制。问题是这种方法不适用于移动语义。

答案 4 :(得分:0)

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

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

我可以想象signals/slots库有点扭曲的实现,它允许复制信号对象,但不允许它们被移动。

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