Есть ли варианты использования для класса, который можно копировать, но не перемещать?

Прочитав этот недавний вопрос @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 и неподвижный? Если нет, то почему не запрещено объявлять конструктор копирования и удаленный конструктор перемещения в одном классе?

6 ответов

Решение

Предположим, у вас есть класс, который не дешевле переместить, чем скопировать (возможно, он содержит std::array типа POD).

Функционально, вы "должны" сделать это MoveConstructible так, чтобы S x = std::move(y); ведет себя как S x = y;и именно поэтому CopyConstructible является суб-концепцией MoveConstructible. Обычно, если вы вообще не объявляете конструкторов, это "просто работает".

На практике, я предполагаю, что вы можете временно отключить конструктор перемещения, чтобы определить, есть ли в вашей программе какой-либо код, который выглядит более эффективным, чем он есть, путем перемещения экземпляров S, Мне кажется чрезмерным запрещать это. Задача стандарта - не навязывать хороший дизайн интерфейса в законченном коде:-)

В настоящее время я не знаю ни одного варианта использования для удаленного конструктора / назначения перемещения. Если это сделано небрежно, то это будет ненужно препятствовать возвращению типа из фабричной функции или помещению в std::vector,

Тем не менее, удаленные участники хода все же являются законными на тот случай, если кто-то может найти для них применение. Как аналогия, я знал, что бесполезно const&& годами. Люди спрашивали меня, не должны ли мы просто объявить это вне закона. Но, в конце концов, несколько случаев использования появились после того, как мы получили достаточный опыт работы с этой функцией. То же самое может случиться и с удаленными участниками перемещения, но, насколько я знаю, пока нет.

Я не думаю, что может быть какой-либо разумный класс, который бы препятствовал перемещению, но позволял бы копировать. Из той же темы ясно, что перемещение - это эффективный способ копирования, когда вам больше не нужен исходный объект.

Я смотрел на эту проблему сегодня, потому что мы портировали некоторый код из 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 был использован для первоначального поиска, затем он был бы скопирован, если это была вставка. Проблема в том, что этот подход не работает с семантикой перемещения.

Это зависит от того, как вы определяете семантику операции перемещения для вашего типа. Если перемещение означает просто оптимизированное копирование с помощью кражи ресурсов, то ответ, вероятно, нет. Но ответ может быть положительным, если перемещение означает "перемещение" в том смысле, в каком оно используется движущимся сборщиком мусора или какой-либо другой пользовательской схемой управления памятью.

Рассмотрим реальный пример дома, расположенного по определенному адресу улицы. Можно определить копию этого дома как другой дом, построенный по тем же чертежам, но расположенный по другому адресу. В соответствии с этими условиями мы можем продолжать говорить, что дом не может быть перемещен, потому что могут быть люди, которые ссылаются на него по его адресу. В переводе на технические термины операция перемещения может быть невозможна для структур, имеющих указатели на вход.

Я могу представить себе немного искаженную реализацию библиотеки сигналов / слотов, которая позволяет копировать объекты сигналов, но не позволяет их перемещать.

Отказ от ответственности: некоторые пуристы C++ будут указывать, что STL (и, следовательно, стандарт) определяет, что такое операция перемещения, и она не соответствует тому, что я описал здесь, поэтому я не буду спорить с этим.

Возьмем квалифицированные объекты: вы можете копировать (из) их (если нет других причин, по которым вы не можете этого сделать), но перемещение (из) их невозможно по смыслу, потому что для любого фактического переноса перемещаемый объект должен быть видоизменен.

Это рассуждение распространяется на классы, которые концептуально, но на самом деле таковыми не являются.constквалифицированный.

Другие вопросы по тегам